-
내가 필요한 다이어리4Swift/다이어리 2024. 4. 16. 23:29
CoreData 죽인다
https://velog.io/@nala/iOS-SwiftUI%EC%97%90%EC%84%9C-CoreData-%EC%8D%A8%EB%B3%B4%EA%B8%B0
[iOS] SwiftUI에서 CoreData 써보기
이 글을 쓰는 나와 나를 지켜보는 n년차 개발자
velog.io
SwiftUI : @FetchRequest 속성 래퍼 [Core Data #1]
Core Data? Core Data란 기본적으로 내부에 저장된 데이터 베이스입니다. 아이폰을 사용하여 데이터를 저장할 수 있습니다. 이 데이터는 세션 간에 유지되므로 사용자가 앱을 닫고 다시 열었을 때 데
seons-dev.tistory.com
두 블로거 때문에 많이 배웠습니다..
일단 나는 캘린더를 누르고 나서
해당 날짜에 일기 쓰고, 사진 넣고, 지출 기록하고, 감정 넣고 하는 사람이어서
.xcdatamodeld 파일이 정말 이해가 안 됐음
두 블로거에게서 어떻게든 뽑아서 해봤는데
결론은! 일단 완벽히 안 건 아니다
일단 요런 식으로 만들어봤는데 (Item은 무시)
날짜 필요하고... 거기에 이미지, 메모, 그리고 관계에 money랑 emotion 추가해서 날짜 하나로 전부 볼 수 있게끔 했다는 말씀
나중에 다시 기록한 것을 보기 전까지 잘 짠 건지는 모르겠지만 저장하는 데는 아무 이상이 없었다!(이상은 있었고.. 아직 적용이 미흡해서 다른 오류였을 수도 있는데 다시 짰다..)
특히 image를 Binary Data로 해야 하는 게 귀찮았음 ㅜ
그럼 차근차근 코드
func fetchMemoForDate(date: String) -> String? { let context = PersistenceController.shared.container.viewContext // CoreData에서 해당 날짜에 맞는 메모를 검색 let fetchRequest: NSFetchRequest<DiaryDate> = DiaryDate.fetchRequest() // CoreData에서 날짜에 해당하는 메모를 찾기 위한 predicate 설정 let predicate = NSPredicate(format: "dateString == %@", date) fetchRequest.predicate = predicate do { // CoreData에서 메모를 가져오기 let result = try context.fetch(fetchRequest) // 결과가 있는 경우 메모 반환 if let memo = result.first { return memo.memo } else { // 결과가 없는 경우 return nil } } catch { // 에러 발생 시 처리 print("Error fetching memo for date: \(error.localizedDescription)") return nil } }
요런 필터링하는?
CoreData에서 필터기 같은 존재인 것 같은데 이걸로 원하는 값을 찾을 수 있는 그런 역할
그래서 CoreData에 저장이 되면 저장된 것 중에 첫 번째 거(?)를 보여주는 기능
여기에 NSFetchRequest가 있어서 꼭 import CoreData를 해야 함
if let memo = fetchMemoForDate(date: dateFormat) { // 메모가 있을 경우 표시 Text(memo) .frame(maxWidth: .infinity, maxHeight: .infinity) // 돌아보기로 가는 버튼 Button { print("돌아보기") self.gotoMemo.toggle() } label: { Text("돌아보기") .frame(maxWidth: .infinity) .frame(height: 40) .font(.system(size:16, weight: .medium)) .foregroundColor(.black) .background( RoundedRectangle(cornerRadius: 10) .fill(Color.blue)) } } else { // 메모가 없을 경우 기본 문구 표시 Text("작성된 일기가 없습니다.") .frame(maxWidth: .infinity, maxHeight: .infinity) // 기록하기로 가는 버튼 Button { print("기록하기") self.gotoMemo.toggle() } label: { Text("기록하기") .frame(maxWidth: .infinity) .frame(height: 40) .font(.system(size:16, weight: .medium)) .foregroundColor(.black) .background( RoundedRectangle(cornerRadius: 10) .fill(Color.gray)) } }
그래서 fetchMemoForDate(date: dateFormat)을 사용해서 보여주거나 말거나
버튼이 바뀌거나 말거나 하게 구현
// CalendarView NavigationLink(destination: ImageMemoView(gotoRoot: self.$gotoMemo, dateFormat: self.$dateFormat), isActive: self.$gotoMemo, label: {}) // ImageMemoView NavigationLink(destination: MoneyView(gotoRoot: self.$gotoRoot, dateFormat: $dateFormat, imageData: self.$image, memoString: self.$memoString), isActive: self.$gotoMoney, label: {}) // MoneyView NavigationLink(destination: EmotionView(gotoRoot: self.$gotoRoot, dateFormat: $dateFormat, imageData: self.$imageData, memoString: self.$memoString, expenseItems: $expenseItems), isActive: self.$gotoEmo, label: {}) // EmotionView NavigationLink(destination: PreviewView(gotoRoot: self.$gotoRoot, dateFormat: $dateFormat, imageData: self.$imageData, memoString: self.$memoString, expenseItems: $expenseItems, emotions: $emotions).environment(\.managedObjectContext, persistenceController.container.viewContext), isActive: self.$gotoPre, label: {})
그리고 @State / @Binding을 사용해서 마지막에 CoreData에 저장하려고 NavigationLink에 정보를 넣어주는데..
이렇게 가면 갈수록 길어지는 거 맞나..?
나중에 한번 이거에 대해서 고민 좀 해보자고
그리고 CoreData작업이 필요한 화면으로 넘겨줄 때
let persistenceController = PersistenceController.shared이거랑(가고 싶은 뷰)().environment(\.managedObjectContext, persistenceController.container.viewContext)를 작성해줘야 함// EmotionView 부분처럼 뭐 길게~ 붙여야 한다그냥 시작하는 화면에 NavigationVeiw 해주고 한 번 적용해주면 되는 것이었던..
import SwiftUI @main struct DiaryApp: App { let persistenceController = PersistenceController.shared var body: some Scene { WindowGroup { NavigationView { CalendarView() .environment(\.managedObjectContext, persistenceController.container.viewContext) } } } }
그리고 처음에 CoreData 체크하고 만들면 persistenceController라는 게 있는데
import CoreData struct PersistenceController { static let shared = PersistenceController() static var preview: PersistenceController = { let result = PersistenceController(inMemory: true) let viewContext = result.container.viewContext for _ in 0..<10 { let newItem = Item(context: viewContext) newItem.timestamp = Date() } do { try viewContext.save() } catch { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. let nsError = error as NSError fatalError("Unresolved error \(nsError), \(nsError.userInfo)") } return result }() let container: NSPersistentContainer init(inMemory: Bool = false) { container = NSPersistentContainer(name: "Diary") if inMemory { container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") } container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. /* Typical reasons for an error here include: * The parent directory does not exist, cannot be created, or disallows writing. * The persistent store is not accessible, due to permissions or data protection when the device is locked. * The device is out of space. * The store could not be migrated to the current model version. Check the error message to determine what the actual problem was. */ fatalError("Unresolved error \(error), \(error.userInfo)") } }) container.viewContext.automaticallyMergesChangesFromParent = true } }
Persistence 파일도 같이 생기면서 안에 내용이다
Preview 부분은 거의 필요 없다고 보면 되고 위에 shared 선언한 거랑 중간부터 let container부분부터는 지우지 말자~
CoreDataStack은 복잡하다는데 persistent container가 캡슐화를 하고 있어 persistent container만 만들어주면 모두 관리 가능
*클래스 안에 서로 연관 있는 속성과 기능들을 하나의 캡슐(capsule)로 만들어 데이터를 외부로부터 보호하는 것
그래서 어떻게 만들었냐!
@Environment(\.managedObjectContext) private var viewContext @FetchRequest(entity: DiaryDate.entity(), sortDescriptors: []) var imageMemo: FetchedResults<DiaryDate> // Binding하기 @Binding var gotoRoot: Bool @Binding var dateFormat: String @Binding var imageData: UIImage? @Binding var memoString: String @Binding var expenseItems: [ExpenseItem] @Binding var emotions: Array<Int16>
위에 두 줄은 저장할 파일에 꼭 넣어야 하는 문구인데(꼭은 아니고 상황에 따라?
아직 잘 못써먹어서 필요한 경우(?)만 사용했는데 저장된 데이터를 바로바로 보여주는 것이 FetchRequest 기능)
그 CoreData 파일에서 만들었던 entity들을 가져와서 저장하려고 있는 듯
그리고 무수히 많은 @Binding..
// CoreData에 추가 private func addItem() { let newDiary = DiaryDate(context: viewContext) // UIImage를 Data로 변환 if let uiImage = imageData { if let imageData = uiImage.jpegData(compressionQuality: 1.0) { newDiary.image = imageData } } newDiary.memo = memoString newDiary.dateString = dateFormat for item in expenseItems { newDiary.money?.category = item.category.category newDiary.money?.price = item.price newDiary.money?.detail = item.detail } newDiary.emotion?.happy = emotions[0] newDiary.emotion?.sad = emotions[1] newDiary.emotion?.angry = emotions[2] newDiary.emotion?.panic = emotions[3] newDiary.emotion?.anxiety = emotions[4] saveItems() } // CoreData 저장 private func saveItems() { do { try viewContext.save() } catch { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. let nsError = error as NSError fatalError("Unresolved error \(nsError), \(nsError.userInfo)") } }
일단 CRUD가 중요한 것 같은데
나는 추가하는 것부터! 그리고 저장
그래서 addItem 함수를 만들고 저장 버튼을 만들어서
호출하면! 저장이 된다는~
구조를 아는데 너무 오래 걸린..
아직 전부를 안 건 아니지만 이젠 좀 쓸 줄 알아졌고...
쓸 줄 알아졌다는 게 큰 경험이고...
얼른 만들고 나서 더 파헤쳐봐야겠다..
'Swift > 다이어리' 카테고리의 다른 글
내가 필요한 다이어리5 (0) 2024.04.18 내가 필요한 다이어리3 (1) 2024.04.15 내가 필요한 다이어리2 (0) 2024.04.14 내가 필요한 다이어리1 (0) 2024.04.13