목차
애플리케이션에서 복잡한 데이터 구조를 관리해야 할 때가 많습니다. 특히, Swift는 그러한 목적을 위한 훌륭한 도구를 제공합니다. 이 블로그 포스트에서는 "SwiftDataSample"이라는 샘플 프로젝트를 통해 SwiftData를 사용하여 관계형 데이터를 구현한 방법을 공유하고자 합니다. 자세한 내용을 살펴보겠습니다.
SwiftData 소개
SwiftData는 Swift에서 데이터 관리를 위한 강력한 라이브러리입니다. 이는 객체 간의 관계를 정의하고, 데이터베이스 연동을 쉽게 할 수 있도록 도와줍니다. 이번 프로젝트에서는 SwiftData를 활용하여 사용자의 이름과 기술 스택 태그를 관리하는 간단한 애플리케이션을 만들어 보았습니다.
프로젝트 구성
이 프로젝트는 세 가지 주요 파일로 구성되어 있습니다: ContentView.swift, Item.swift, 그리고 SwiftDataSampleApp.swift.
Item.swift 및 Tag.swift
이 글의 핵심인 Item 과 Tag Class 입니다. 우선 관계를 구성하기에 앞서 ERD로 표현 해보겠습니다.
1개의 Item에 여러개의 Tag를 넣을 수 있습니다. (1:N 구조) 추가적으로 Tag는 필수로 들어갈 필요가 없어 보입니다.
//
// Item.swift
// SwiftDataSample
//
// Created by 고종찬 on 2023/12/20.
//
import Foundation
import SwiftData
@Model
final class Item {
@Attribute(.unique) var id = UUID()
var name: String
@Relationship(inverse: \Tag.item) var tags: [Tag]?
init(name: String) {
self.name = name
}
}
@Model
final class Tag{
var name: String
// Relationship with Item
var item: Item?
init(name: String) {
self.name = name
}
}
Item과 Tag 클래스는 데이터 모델을 나타냅니다. Item은 사용자의 이름을, Tag는 기술 스택 태그를 저장합니다. 이들 사이의 관계는 @Relationship을 통해 정의됩니다.
Item 클래스의 @RelationShip을 보면
아까전에도 언급한 것과 동일하게 Tag를 Optional로 설정을하여 필수가 아님을 나타냅니다.
또한 Tag의 item을 선언을 하여 두 Class간에 관계를 맺어졌다는 것을 확인 할 수 있습니다.
ContentView.swift
//
// ContentView.swift
// SwiftDataSample
//
// Created by 고종찬 on 2023/12/20.
//
import SwiftUI
import SwiftData
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var items: [Item]
@State private var nameText = ""
@State private var tagText = ""
var body: some View {
NavigationStack {
TextField("Insert Name", text: $nameText)
.padding()
TextField("Insert Tech Stack Tag", text: $tagText)
.padding()
List {
ForEach(items) { item in
NavigationLink {
Text("Hello \(item.name)")
} label: {
VStack(alignment: .leading){
Text("\(item.name)")
.font(.title)
HStack{
ForEach(item.tags ?? []){ tag in
Text("#\(tag.name)")
.font(.caption)
}
}
}
}
}
.onDelete(perform: deleteItems)
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
.disabled(nameText.isEmpty)
}
}
}
}
private func addItem() {
withAnimation {
let newItem = Item(name: self.nameText)
let tags = tagText.split(separator: ",").map { Tag(name: String($0)) }
newItem.tags = tags
modelContext.insert(newItem)
self.nameText = ""
self.tagText = ""
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
for index in offsets {
modelContext.delete(items[index])
}
}
}
}
#Preview {
ContentView()
.modelContainer(for: Item.self, inMemory: true)
}
여기서 사용자는 이름과 기술 스택 태그를 입력할 수 있으며, 이 데이터는 리스트에 표시됩니다. 사용자는 각 항목을 삭제하거나 추가할 수도 있습니다. 이 과정에서 @Environment, @Query, 그리고 @State와 같은 SwiftData의 주요 프로퍼티 래퍼들이 활용됩니다.
addItem 함수를 보시면 tag의 item을 넣는 로직이 나타납니다. (저는 ","을 이용해서 String을 분리하여 사용해보았습니다.)
SwiftDataSampleApp.swift
//
// SwiftDataSampleApp.swift
// SwiftDataSample
//
// Created by 고종찬 on 2023/12/20.
//
import SwiftUI
import SwiftData
@main
struct SwiftDataSampleApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Item.self, Tag.self
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(sharedModelContainer)
}
}
이 파일은 애플리케이션의 진입점을 정의합니다. ModelContainer를 설정하여 데이터 스키마를 구성하고, 이를 애플리케이션 전반에 걸쳐 사용합니다.
데이터 관리의 핵심
이 프로젝트의 핵심은 ContentView에서 데이터를 추가하고 삭제하는 과정입니다. 사용자가 입력한 이름과 태그는 addItem 함수를 통해 Item 객체로 변환되고, 데이터베이스에 저장됩니다. deleteItems 함수는 사용자가 선택한 항목을 삭제합니다.
구현
하나의 아이템에 여러개의 Tag가 들어가 있을 수 있고 (배열 형식으로 구현)
하지만 하나의 아이템에 Tag가 들어있지 않을 수 있다는 것을 보여드렸습니다.
(Optional 구현)
결론
SwiftData를 사용하면 SwiftUI와 결합하여 강력하고 유연한 데이터 관리 시스템을 구축할 수 있습니다. 이 예제를 통해 SwiftData의 기본적인 사용 방법과 관계형 데이터 모델링을 어떻게 구현하는지 보여주고자 했습니다.
'iOS' 카테고리의 다른 글
[Swift] Struct(구조체)? Class(클래스)? (0) | 2024.06.15 |
---|---|
[SwiftUI] SwiftUI에서 탭바 숨기기 및 커스텀 백 버튼 만들기 (0) | 2024.06.13 |
[SwiftUI] 데이터 흐름: @Binding vs 단순 변수,상수 (0) | 2023.12.19 |
[Swift] 권한 요청 사용 + 구현하기 (feat. SwiftUI) (0) | 2023.12.01 |
[Swift] 권한 요청 정리 (0) | 2023.12.01 |