이제 생성한 List를 CoreData에 저장해야 한다.
MyList의 clolor attribute는 지원하지 않는 타입의 데이터를 저장하기 휘애 Transformable로 설정돼있고, 형변환을 위해 transformer가 필요하다.
//
// UIColorTransformer.swift
// ReminderApp
//
import Foundation
import UIKit
class UIColorTransformer: ValueTransformer {
override func transformedValue(_ value: Any?) -> Any? {
guard let color = value as? UIColor else { return nil }
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: color, requiringSecureCoding: true)
return data
} catch {
return nil
}
}
override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let data = value as? Data else { return nil }
do {
let color = try NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data)
return color
} catch {
return nil
}
}
}
위에서 지정한 Transformer와 동일한 이름으로 transformer를 정의한다.
해당 클래스는 두가지 메서드를 가지고 있고, transformedValue 메서드는 UIColor를 Data로 변환하는 역할을 하고 있다.
매커니즘은 다음과 같다.
- 입력되는 데이터가 UIColor 타입인지 확인한다.
- NSKeyedArchiver를 사용해 UIColor 타입의 데이터를 Data 타입으로 변환한다. 이 때 requireingSecureCoding을 true로 사용해 보안성을 강화한다. 지금은 큰 의미가 없지만 보통은 켜놓는 것이 추천된다.
- 입력된 데이터가 UIColor가 아니거나 변환에 문제가 있다면 nil을 반환한다.
나머지 reverseTransromedValue 메서드는 코어데이터에서 받아오는 Data를 다시 UIColor로 되돌리는 역할을 한다.
매커니즘은 다음과 같다.
- 입력되는 데이터가 Data 타입인지 확인한다.
- NSKeyedUnarchiver를 사용해 Data 타입의 데이터를 UIColor 타입으로 변환한다.
- 입력된 데이터가 Data타입이 아닌거나 변환에 문제가 있다면 nil을 반환한다.
//
// CoreDataProvider.swift
// ReminderApp
//
import Foundation
import CoreData
class CoreDataProvider {
static let shared = CoreDataProvider()
let persistentContainer: NSPersistentContainer
private init() {
//register transformers
ValueTransformer.setValueTransformer(UIColorTransformer(), forName: NSValueTransformerName("UIColorTransformer"))
persistentContainer = NSPersistentContainer(name: "ReminderModel")
persistentContainer.loadPersistentStores { NSEntityDescription, error in
if let error {
fatalError("Error initializing ReminderModel \(error)")
}
}
}
}
정의된 transformer는 CoreDataProvider에 attribute의 내용에 작성한 것과 같은 이름으로 선언해 둔다.
이렇게 해 두면 해당 데이터를 변환할 때 해당 transformer 클래스를 자동으로 사용하게 된다.
//
// ReminderAppApp.swift
// ReminderApp
//
import SwiftUI
@main
struct ReminderAppApp: App {
var body: some Scene {
WindowGroup {
HomeView()
.environment(\.managedObjectContext, CoreDataProvider.shared.persistentContainer.viewContext)
}
}
}
이렇게 작성된 CoreDataProvider는 앱 전체에서 공용으로 사용할 수 있도록 environment로 전달한다.
//
// ReminderService.swift
// ReminderApp
//
import Foundation
import CoreData
import UIKit
class ReminderService {
static var viewContext: NSManagedObjectContext {
CoreDataProvider.shared.persistentContainer.viewContext
}
static func save() throws {
try viewContext.save()
}
static func saveMyList(_ name: String, _ color: UIColor) throws {
let myList = MyList(context: viewContext)
myList.name = name
myList.color = color
try save()
}
}
ReminderService 클래스는 본격적인 CoreData와 상호작용하는 메서드를 가지게 된다.
CoreDataProvider 싱글톤 인스턴스이기 때문에 해당 클래스 하나로 중앙 집중식 서비스가 가능하다.
지금은 두 개의 메서드를 작성했고, 하나는 저장의 기능을 담당하는 간단한 save 메서드와 실질적인 List 데이터를 저장하기 위한 saveMyList 메서드이다.
//
// AddNewListView.swift
// ReminderApp
//
import SwiftUI
struct AddNewListView: View {
@Environment(\.dismiss) private var dismiss
@State private var name: String = ""
@State private var selectedColor: Color = .yellow
let onSave: (String, UIColor) -> Void
private var isFormValid: Bool {
!name.isEmpty
}
var body: some View {
VStack {
VStack {
Image(systemName: "line.3.horizontal.circle.fill")
.foregroundColor(selectedColor)
.font(.system(size: 100))
TextField("List Name", text: $name)
.multilineTextAlignment(.center)
.textFieldStyle(.roundedBorder)
}
.padding(30)
.clipShape(RoundedRectangle(cornerRadius: 10.0, style: .continuous))
ColorPickerView(selectedColor: $selectedColor)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.toolbar {
ToolbarItem(placement: .principal) {
Text("New List")
.font(.headline)
}
ToolbarItem(placement: .topBarLeading) {
Button("Close") {
dismiss()
}
}
ToolbarItem(placement: .topBarTrailing) {
Button("Done") {
// TODO: save function
onSave(name, UIColor(selectedColor))
dismiss()
}
.disabled(!isFormValid)
}
}
}
}
해당 saveMyList 메서드는 AddNewListView의 onSave와 연계돼 동작하는데, Done 버튼을 누르면 해당 클로저가 호출되고,
//
// ContentView.swift
// ReminderApp
//
import SwiftUI
struct HomeView: View {
@State private var isPresented: Bool = false
var body: some View {
NavigationStack {
VStack {
Text("Hello World")
Spacer()
HStack {
Button {
isPresented = true
} label: {
Text("Add List")
.font(.headline)
}
.padding()
}
.frame(maxWidth: .infinity, alignment: .bottomTrailing)
}
.sheet(isPresented: $isPresented, content: {
NavigationStack {
AddNewListView { name, color in
do {
try ReminderService.saveMyList(name, color)
} catch {
print(error.localizedDescription)
}
}
}
})
}
.padding()
}
}
HomeView에서 해당 closure를 받아 saveMyList 메서드를 호출해 새롭게 작성된 List를 저장한다.
'프로젝트 > ReminderApp clone' 카테고리의 다른 글
07. 기능개선 #1 (공백 예외처리하기) (0) | 2024.04.17 |
---|---|
06. ListView 구성하기, Preview Data 구성하기 (0) | 2024.04.17 |
03. AddNewListView 수정 및 호출 (0) | 2024.04.11 |
02. 새 List 추가 인터페이스 구현하기 (0) | 2024.04.11 |
01. CoreData 설계(01) (0) | 2024.04.11 |