[SwiftUi] SwiftData 이해하기 - 1
사용자 로그인 없이 사용자별 즐겨찾기 기능을 만들기 위해 로컬데이터를 사용해야하는 상황이 생겼다.
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