[SwiftUI] CoreData & MVVM

Junyoung Park·2022년 8월 19일
0

SwiftUI

목록 보기
15/136
post-thumbnail

How to use Core Data with MVVM Architecture in SwiftUI | Continued Learning #15

CoreData & MVVN

  • FetchRequest를 통해 코어 데이터를 다룰 수 있는 방법이 있다.
  • MVVM적으로 코드를 분리, 보다 사용이 편리하도록 만들어보기
  • 모델, 뷰 모델, 뷰를 분리한 디자인 패턴

구현 목표

구현 태스크

  1. 코어 데이터 매니저 클래스
  2. 데이터 바인딩 연동: ObservableObject, StateObject 사용
  3. 코어 데이터 CRUD

핵심 코드

    func addFruit(name: String) {
        let fruit = FruitEntity(context: container.viewContext)
        fruit.name = name
        saveData()
    }
    
    func deleteFruit(indexSet: IndexSet) {
        guard let index = indexSet.first else { return }
        let entity = savedEntities[index]
        container.viewContext.delete(entity)
        saveData()
    }
    
    func saveData() {
        do {
            try container.viewContext.save()
            fetchFruits()
        } catch {
            print("ERROR SAVING CORE DATA")
            print(error.localizedDescription)
        }
    }
    
    func updateFruit(entity: FruitEntity, name: String) {
        entity.name = name
        saveData()
    }
  • viewContext를 기반으로 데이터의 스냅샷 정보를 바로 저장하는 코어 데이터는 기본적으로 add, delete, update 모두 saveData를 사용하고 있다는 데 주의하자.

소스 코드

import SwiftUI
import CoreData

// VIEW - UI
// MODEL - Data Point
// VIEW MODEL - Manager between data and view

class CoreDataViewModel: ObservableObject {
    let container: NSPersistentContainer
    @Published var savedEntities = [FruitEntity]()
    init () {
        container = NSPersistentContainer(name: "FruitsContainer")
        container.loadPersistentStores { description, error in
            if let error = error {
                print("ERROR LOADING CORE DATA")
                print(error.localizedDescription)
            } else {
                print("SUCCESSFULLY LOAD CORE DATA")
            }
        }
        fetchFruits()
    }
    
    func fetchFruits() {
        let request = NSFetchRequest<FruitEntity>(entityName: "FruitEntity")
        do {
            savedEntities = try container.viewContext.fetch(request)
        } catch {
            print("ERROR FETCHING CORE DATA")
            print(error.localizedDescription)
        }
    }
    
    func addFruit(name: String) {
        let fruit = FruitEntity(context: container.viewContext)
        fruit.name = name
        saveData()
    }
    
    func deleteFruit(indexSet: IndexSet) {
        guard let index = indexSet.first else { return }
        let entity = savedEntities[index]
        container.viewContext.delete(entity)
        saveData()
    }
    
    func saveData() {
        do {
            try container.viewContext.save()
            fetchFruits()
        } catch {
            print("ERROR SAVING CORE DATA")
            print(error.localizedDescription)
        }
    }
    
    func updateFruit(entity: FruitEntity, name: String) {
        entity.name = name
        saveData()
    }
}

struct CoreDataBootCamp: View {
    @StateObject private var viewModel = CoreDataViewModel()
    @State private var textFieldText: String = ""
    @State private var isAdd: Bool = true
    private var textFieldPlaceholder: String {
        return isAdd ? "Add New Fruit here..." : "Update Fruit name here..."
    }
    @State private var fruitEntity: FruitEntity? = nil
    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
                TextField(textFieldPlaceholder, text: $textFieldText)
                    .font(.headline)
                    .padding(.leading)
                    .frame(height: 55)
                    .background(Color.gray.opacity(0.1))
                    .cornerRadius(10)
                    .padding(.horizontal)
                Button(action: {
                    guard !textFieldText.isEmpty else { return }
                    if isAdd {
                        viewModel.addFruit(name: textFieldText)
                    } else {
                        guard let fruitEntity = fruitEntity else { return }
                        viewModel.updateFruit(entity: fruitEntity, name: textFieldText)
                    }
                    isAdd = true
                    textFieldText = ""
                }, label: {
                    Text("Submit")
                        .font(.headline)
                        .foregroundColor(.white)
                        .frame(height: 55)
                        .frame(maxWidth: .infinity)
                        .background(Color.pink)
                        .cornerRadius(10)
                        .padding(.horizontal)
                })
                List {
                    ForEach(viewModel.savedEntities) { entity in
                        Text(entity.name ?? "No Name")
                            .onTapGesture {
                                textFieldText = ""
                                isAdd = false
                                fruitEntity = entity
                            }
                    }
                    .onDelete(perform: viewModel.deleteFruit)
                }
                .listStyle(.plain)
            }
            .navigationTitle("Fruits")
        }
    }
}
  • 텍스트 필드가 새로운 데이터 입력 + 데이터 선택 시 새로운 이름 입력 두 역할을 하고 있다. 플레이스 홀더(연산 프로퍼티)를 통해 현재 어떤 태스크를 진행 중인지 표시했다.
  • 리스트를 통해 코어 데이터 내 저장된 아이템 배열이 표현되고 있는데, 인덱스셋을 통해 간접적으로, 엔티티를 통해 직접적으로 데이터 접근이 가능하다는 데 주의하자.

구현 화면

profile
JUST DO IT

0개의 댓글