Swift/Swift 개발 노트

[SwiftUi] SwiftData 이해하기 - 1

힛해 2024. 3. 12. 16:42
728x90

 

사용자 로그인 없이 사용자별 즐겨찾기 기능을 만들기 위해 로컬데이터를 사용해야하는 상황이 생겼다.

 

CoreData와 SwiftData중 최근에 생긴 SwiftData를 선택해 개발하기로 했다. ( 공식문서 보는 습관을 들이기 위해서 )

 

SwiftData는 @Model을 스키마를 생성한다

  • Swift 코드에서 모델의 스키마를 정의하는 데 사용됩니다.
  • 클래스를 장식하는걸로 간단하게 스키마를 생성할 수 있습니다.
import Foundation
import SwiftData

@Model
final class ToDoItem {
    var title: String
    var timestamp: Date
    var isCritical: Bool
    var isCompleted: Bool

    init(title: String = "",
         timestamp: Date = .now,
         isCritical: Bool = false,
         isCompleted: Bool = false) {
        self.title = title
        self.timestamp = timestamp
        self.isCritical = isCritical
        self.isCompleted = isCompleted
    }
}

 

title, timestamp 들의 자료형을 정의하고 init(){} 을 통해 초기값을 설정한다.

 

import SwiftUI
struct CreateTodoView: View {
    
    @Environment(\.dismiss) var dismiss
    @Environment(\.modelContext) var context
    @State private var item = ToDoItem()
    
    var body: some View {
        List {
            TextField("제목", text: $item.title)
            DatePicker("날짜를 선택하십씨오",
                       selection: $item.timestamp)
            Toggle("중요한가요?", isOn: $item.isCritical)
            Button("생성") {
                withAnimation {
                    context.insert(item)
                }
                dismiss()
            }
        }
        .navigationTitle("일정 만들기")
    }
}

 

@Environment(\.dismiss) var dismiss

[var dismiss: DismissAction]

(<https://developer.apple.com/documentation/swiftui/environmentvalues/dismiss>)

현재 프레젠테이션을 닫는 작업입니다.

@Environment(\.modelContext) var context

 

🌱 Tracking updates
🌱 Fetching model
🌱 Saving changes
🌱 Undoing changes

업데이트 추적, 데이터 가져오기, 변경 사항 저장하기, 변경 사항 실행 쉬소하기 기능의 인터페이스입니다.


@State private var item = ToDoItem()

변수 item을 ToDoItem의 자료형으로 할당합니다.

 

struct ContentView: View {
    
    @Environment(\.modelContext) var context
    @State private var showCreate = false
    @State private var toDoToEdit: ToDoItem?
    @Query(sort: \ToDoItem.timestamp) private var items: [ToDoItem]

 

@Query(sort: \ToDoItem.timestamp) timestamp를 기준으로 정렬하고 items에 할당한다.

 

predecate를 이용한 방법이다

🪴 Predicate

let today = Date()
let tripPredicate = #Predicate<Trip> { 
    $0.destination == "New York" && // 목적지 : 뉴욕
    $0.name.contains("birthday") && // 키워드 : 생일
    $0.startDate > today // 오늘 이후, 계획된 여행 탐색
}

 

let descriptor = FetchDescriptor<Trip>(
    sortBy: SortDescriptor(\Trip.name),
    predicate: tripPredicate
)

let trips = try context.fetch(descriptor)

 

@Query를 사용해 여러 조건을 넣을떄의 방법이다.

@Query(sort: [SortDescriptor(\Destination.priority, order: .reverse), SortDescriptor(\Destination.name)]) var destinations: [Destination]
@State private var sortOrder = SortDescriptor(\Destination.name)

이렇게도 사용한다.

 

추후 정렬을 할떄 사용하기위해

Menu("Sort", systemImage: "arrow.up.arrow.down") {
    Picker("Sort", selection: $sortOrder) {
        Text("Name")
            .tag(SortDescriptor(\Destination.name))

        Text("Priority")
            .tag(SortDescriptor(\Destination.priority, order: .reverse))

        Text("Date")
            .tag(SortDescriptor(\Destination.date))
    }
    .pickerStyle(.inline)
}

DestinationListingView셋째, 쿼리를 사용하기 위한 대상 정렬 순서를 허용하도록 이니셜라이저를 추가해야 합니다 . 반환된 데이터 배열이 아닌 쿼리 자체를 변경하려고 하기 때문에 다음과 같이 밑줄이 그어진 속성 이름을 사용해야 합니다.

init(sort: SortDescriptor<Destination>) {
    _destinations = Query(sort: [sort])
}

넷째, 샘플 정렬 옵션을 전달하도록 미리보기 코드를 업데이트해야 합니다.

#Preview {
    DestinationListingView(sort: SortDescriptor(\Destination.name))
}

마지막으로 다음과 같이 ContentView정렬 값을 로 보내도록 조정해야 합니다 .DestinationListingView

DestinationListingView(sort: sortOrder)

 

🌿 Modifying your data

ModelContext를 사용하여 이러한 작업을 구동함으로써 데이터를 쉽게 생성, 삭제 및 변경할 수 있습니다.

🌱 Basic operations (CRUD)

  • Inserting
  • Deleting
  • Saving
  • Changing
var myTrip = Trip(name: "Birthday Trip", destination: "New York")

// Insert a new trip
context.insert(myTrip)

// Delete an existing trip
context.delete(myTrip)

// Manually save changes to the context
try context.save()

@Model 매크로는 ModelContext가 변경 사항을 자동으로 추적하고 다음 저장 작업에 포함하도록 돕는다고 합니다.

🌲 Using SwiftData with SwiftUI

  • SwiftUI와의 원활한 통합
  • 쉬운 구성
  • 자동으로 데이터 Fetching

🌿 View Modifiers

데이터 저장소를 구성하고, 옵션을 변경하고, 실행 취소를 활성화하고, 자동 저장을 전환할 수 있습니다.

🌱 Leverage scene and view modifiers (뭐라고 해석하면 좋을지 혹시 아시는 분 계실까요 ?)
🌱 .modelContainer를 사용하여 데이터 저장소 구성
🌱 SwiftUI environment로 등록하여 사용하기

설정한 후 데이터 사용을 시작하는 가장 쉬운 방법은 새로운 @Query 속성 래퍼입니다.
한 줄의 코드로 데이터베이스에 저장된 모든 항목을 쉽게 로드하고 필터링할 수 있습니다.

수정하기

struct UpdateToDoView: View {
    
    @Environment(\.dismiss) var dismiss
    
    @Bindable var item: ToDoItem
    
    var body: some View {
        List {
            TextField("제목", text: $item.title)
            DatePicker("날짜를 선택해주세요",
                       selection: $item.timestamp)
            Toggle("중요한가요?", isOn: $item.isCritical)
            Button("수정") {
                dismiss()
            }
        }
        .navigationTitle("수정하기")
    }
}

 

자동으로 관리해주기때문애 context.insert()를 하지 않아도 값이 저장된다.

전체코드

struct ContentView: View {
    
    @Environment(\.modelContext) var context
    @State private var showCreate = false
    @State private var toDoToEdit: ToDoItem?
    @Query(sort: \ToDoItem.timestamp) 
    private var items: [ToDoItem]
    
    var body: some View {
        
        NavigationStack {
            List {
                ForEach(items) { item in
                    HStack {
                        VStack(alignment: .leading) {
                            
                            if item.isCritical {
                                Image(systemName: "exclamationmark.3")
                                    .symbolVariant(.fill)
                                    .foregroundColor(.red)
                                    .font(.largeTitle)
                                    .bold()
                            }
                            
                            Text(item.title)
                                .font(.largeTitle)
                                .bold()
                            
                            Text("\(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .shortened))")
                                .font(.callout)
                        }
                        
                        Spacer()
                        
                        Button {
                            withAnimation {
                                item.isCompleted.toggle()
                            }
                        } label: {
                            
                            Image(systemName: "checkmark")
                                .symbolVariant(.circle.fill)
                                .foregroundStyle(item.isCompleted ? .green : .gray)
                                .font(.largeTitle)
                        }
                        .buttonStyle(.plain)
                        
                    }
                    .swipeActions {
                        Button(role: .destructive) {
                            
                            withAnimation {
                                context.delete(item)
                            }
                            
                        } label: {
                            Label("Delete", systemImage: "trash")
                                .symbolVariant(.fill)
                        }
                        
                        Button {
                            toDoToEdit = item
                        } label: {
                            Label("Edit", systemImage: "pencil")
                        }
                        .tint(.orange)
                    }
                }
            }
            .navigationTitle("일정추가")
            .toolbar {
                ToolbarItem {
                    Button(action: {
                        showCreate.toggle()
                    }, label: {
                        Label("Add Item", systemImage: "plus")
                    })
                }
            }
            .sheet(isPresented: $showCreate,
                   content: {
                NavigationStack {
                    CreateTodoView()
                }
                .presentationDetents([.medium])
            })
            .sheet(item: $toDoToEdit) {
                toDoToEdit = nil
            } content: { item in
                UpdateToDoView(item: item)
            }
        }
    }
}

 

.sheet()는 해당 값이 참일때 나오는 하단바이다.
수정의 경우 버튼을 누르면 toDoToEdit에 값을 할당하기때문애 sheet가 켜지고 값을 비워주는 로직을 작성한다.

 

출처

https://www.hackingwithswift.com/quick-start/swiftdata/sorting-query-results

https://velog.io/@debby_/SwiftData-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-1