ABOUT ME

Today
Yesterday
Total
  • 내가 필요한 다이어리4
    Swift/다이어리 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

    https://seons-dev.tistory.com/entry/SwiftUI-FetchREquest-%EC%86%8D%EC%84%B1-%EB%9E%98%ED%8D%BC-Core-Data-1#google_vignette

     

    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 > 다이어리' 카테고리의 다른 글

    댓글

Designed by Tistory.