내가 필요한 다이어리4
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 함수를 만들고 저장 버튼을 만들어서
호출하면! 저장이 된다는~
구조를 아는데 너무 오래 걸린..
아직 전부를 안 건 아니지만 이젠 좀 쓸 줄 알아졌고...
쓸 줄 알아졌다는 게 큰 경험이고...
얼른 만들고 나서 더 파헤쳐봐야겠다..