Swift/다이어리

내가 필요한 다이어리4

띵지니 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 함수를 만들고 저장 버튼을 만들어서

호출하면! 저장이 된다는~

 

구조를 아는데 너무 오래 걸린..

아직 전부를 안 건 아니지만 이젠 좀 쓸 줄 알아졌고...

쓸 줄 알아졌다는 게 큰 경험이고...

얼른 만들고 나서 더 파헤쳐봐야겠다..