# 1. 클래스 정의

import Foundation
import SwiftUI

class Memo: Identifiable, ObservableObject {
    let id: UUID
    @Published var content: String
    let insertDate: Date

    init(content: String, insertDate: Date = Date.now) {
        self.id = UUID()
        self.content = content
        self.insertDate = insertDate
    }
}

SwiftUI에서 제공하는 Identifiable 프로토콜, ObservableObject프로토콜을 채택한 클래스를 정의한다. 객체 속성 정의 시 값의 변화에 따라 UI에 실시간 바인딩이 필요한 속성은 @Published 키워드로 선언한다.

# 2. DAO 정의

코어데이터로의 저장 로직을 처리할 DAO 클래스를 정의한다.

class MemoStore: ObservableObject {
    @Published var list: [Memo]

    init() {
        // 더미데이터
        self.list = [
            Memo(content: "day1"),
            Memo(content: "day2", insertDate: Date.now.addingTimeInterval(3600 * -24)),
            Memo(content: "day3", insertDate: Date.now.addingTimeInterval(3600 * -48))
        ]
    }

    func insert(memo: String) {
        list.insert(Memo(content: memo), at: 0)
    }

    func update(memo: Memo?, content: String) {
        guard let memo = memo else { return }
        memo.content = content
    }

    func delete(memo: Memo) {
        list.removeAll { $0.id == memo.id }
    }

    func delete(set: IndexSet) {
        for index in set {
            list.remove(at: index)
        }
    }
}

메모 리스트 속성의 경우 List컴포넌트에 실시간 UI 바인딩을 위해 @Published로 선언한다.

# 3. 메인화면 정의

struct MainListView: View {
    @EnvironmentObject var store: MemoStore

    var body: some View {
        NavigationView {
            List(store.list) { memo in
                VStack(alignment: .leading) {
                    Text(memo.content)
                        .font(.body)
                        .lineLimit(1)

                    Text(memo.insertDate, style: .date)
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
            }
            .navigationTitle("메모")
        }
    }
}

엔트리포인트가 되는 메인 뷰를 정의한다. @EnvironmentObject 키워드 선언을 통해 하위 뷰에서 접근할 DAO속성을 추가한다.

@EnvironmentObject 키워드로 선언된 속성은 자식 뷰에서 부모 뷰의 envorionmentObject에 접근할 수 있게 된다.

NavigationView로 전체 뷰를 래핑하고 뷰 안에 List를 추가하여 테이블 뷰를 삽입한다. 생성자 파라미터에 바인딩할 속성을 전달하고 내부 컴포넌트에 속성값들을 연결해준다.

navigationTitle 모디파이어를 통해 네비게이션 뷰의 타이틀값을 조정할 수 있다.

# 4. 메인화면 리팩토링

import SwiftUI

struct MainListView: View {
    @EnvironmentObject var store: MemoStore
    var body: some View {
        NavigationView {
            List(store.list) { memo in
                MemoCell(memo: memo)
            }
            .navigationTitle("메모")
        }
    }
}

struct MemoCell: View {
    @ObservedObject var memo: Memo

    var body: some View {
        VStack(alignment: .leading) {
            Text(memo.content)
                .font(.body)
                .lineLimit(1)

            Text(memo.insertDate, style: .date)
                .font(.caption)
                .foregroundStyle(.secondary)
        }
    }
}

VStack에서 처리하던 뷰를 별도의 구조체로 분리한다. VStack 클로저 호출 단에서 우클릭하여 Extract Subview를 선택하면 자동으로 새로운 구조체를 정의해준다.

새로 정의된 셀 구조체에 바인딩할 데이터를 속성으로 추가하는데 @ObservedObject 키워드를 붙여 속성 업데이트가 이루어질 때 자동으로 UI 업데이트를 진행하게 된다.