안녕하세요! 피피아노입니다 🎵
이번 포스팅에서는 Core Data를 프로젝트에 사용하는 방법에 대해서 다뤄보려고 합니다.
Core Data는 저번 포스팅에서 다룬 적이 있지만 잘 모르시는 분들을 위해서 간단하게 설명하고 넘어가자면 쉽게 말해서 앱에서 사용할 데이터를 영구적으로 저장하기 위한 도구라고 생각하시면 됩니다! (앱이 삭제되기 전까지!)
Core Data에 대한 포스팅이 궁금하시면 여기를 참고해주세요!
저는 Core Data를 사용해서 제품의 이름과 수량을 저장하는 간단한 인벤토리 앱을 만들어보겠습니다.
Core Data 프로젝트 생성하기
우선 Xcode를 실행하고 새로운 프로젝트를 생성하는 옵션을 선택한 후, Multiplatform App을 선택해주겠습니다.
프로젝트 이름은 아무거나 해주시고 프로젝트 옵션 화면에서 앱을 고유하게 식별하기 위한 Organization Identifier를 지정해야합니다.
Organization Identifier는 보통 소속된 도메인에서 www를 빼고 역순으로 작성합니다. 소속된 도메인이 없으면 비슷하게 양식 맞춰 입력하면 됩니다.
ex) com.ppiano.project
그리고 Storage 부분에서 Core Data로 선택을 해주겠습니다.
그리고 프로젝트를 생성해주면 됩니다.
만약 기존 프로젝트에서 사용하고 싶다면? 프로젝트 화면 하단에 + 버튼을 누르고 file에서 Core Data 부분에서 Data Model 파일을 선택하고 생성해주시면 됩니다!
엔티티 디스크립션 정의하기
저는 좀 더 보기 쉽게 Product라는 이름으로 데이터 파일을 하나 더 추가해서 작업을 해보겠습니다.
파일을 생성하고 화면을 보면 위에 사진처럼 엔티티 에디터 내에 표시될 것입니다.
모델에 새로운 엔티티를 추가하기 위해 하단에 Add Entitiy 버튼을 눌러주겠습니다. 그럼 기본적으로 Entity라는 이름의 새로운 엔티티가 추가될텐데 저는 여기에서도 이름을 Product로 바꿔서 작업을 할게요!
이렇게 엔티티가 생성되었으면 이제 이름과 수량 속성을 추가해주겠습니다. 첫 번째 속성을 추가하기 위해 메인 패널에서 Attributes 아래에 + 버튼을 눌러서 이름은 name 타입은 String으로 설정한 속성과 quantity라는 이름과 String타입을 가진 속성을 추가하겠습니다.
영구 컨트롤러 생성하기
다음으로는 NSPersistentContainer 인스턴스를 생성하고 초기화하는 영구 컨트롤러 클래스를 만들어주겠습니다.
Persistence.swift 파일에 아래 코드를 작성해주겠습니다.
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentContainer
init() {
container = NSPersistentContainer(name: "Product")
container.loadPersistentStores { (storeDescription, error)in
if let error = error as NSError? {
fatalError("Container load failed: \(error)")
}
}
}
}
뷰 콘텍스트 설정과 사용자 인터페이스 설계하기
그리고 CoreDataProjectApp.swift 파일에 아래와 같이 코드를 작성해주겠습니다.
import SwiftUI
@main
struct CoreDataDemoApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext,
persistenceController.container.viewContext)
}
}
}
ContentView
ContentView는 기본 화면으로 제품의 이름과 수량을 입력할 수 있는 텍스트 필드와 추가, 검색, 지우기 버튼을 제공하고 CoreData에서 제품 목록을 가져와 화면에 표시해줍니다.
import SwiftUI
import CoreData
struct ContentView: View {
@State var name: String = ""
@State var quantity: String = ""
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(entity: Product.entity(),
sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)])
private var products: FetchedResults<Product>
var body: some View {
NavigationView {
VStack {
TextField("Product name", text: $name)
TextField("Product quantity", text: $quantity)
HStack {
Spacer()
Button("Add") {
addProduct()
}
Spacer()
NavigationLink(destination: ResultsView(name: name,
viewContext: viewContext)) {
Text("Find")
}
Spacer()
Button("Clear") {
name = ""
quantity = ""
}
Spacer()
}
.padding()
.frame(maxWidth: .infinity)
List {
ForEach(products) { product in
HStack {
Text(product.name ?? "Not found")
Spacer()
Text(product.quantity ?? "Not found")
}
}
.onDelete(perform: deleteProducts)
}
.navigationTitle("Product Database")
}
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}
private func addProduct() {
withAnimation {
let product = Product(context: viewContext)
product.name = name
product.quantity = quantity
saveContext()
}
}
private func deleteProducts(offsets: IndexSet) {
withAnimation {
offsets.map { products[$0] }.forEach(viewContext.delete)
saveContext()
}
}
private func saveContext() {
do {
try viewContext.save()
} catch {
let error = error as NSError
fatalError("An error occured: \(error)")
}
}
}
struct ResultsView: View {
var name: String
var viewContext: NSManagedObjectContext
@State var matches: [Product]?
var body: some View {
return VStack {
List {
ForEach(matches ?? []) { match in
HStack {
Text(match.name ?? "Not found")
Spacer()
Text(match.quantity ?? "Not found")
}
}
}
.navigationTitle("Results")
}
.task {
let fetchRequest: NSFetchRequest<Product> = Product.fetchRequest()
fetchRequest.entity = Product.entity()
fetchRequest.predicate = NSPredicate(
format: "name CONTAINS %@", name
)
matches = try? viewContext.fetch(fetchRequest)
}
}
}
주요 구성 요소
1. 상태 변수 및 환경 변수
- @State var name: String = ""
- @State var quantity: String = ""
- @Environment(\.managedObjectContext) private var viewContext
상태 변수 name과 quantity는 사용자가 입력한 값을 저장합니다. 환경 변수 viewContext는 CoreData의 Managed Object Context를 가져옵니다.
2. Fetch Request
- @FetchRequest(entity: Product.entity(), sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)])
FetchRequest는 CoreData에서 Product 엔티티를 가져오고 이름을 기준으로 오름차순으로 정렬합니다.
3. UI 구성
- NavigationView 내에 VStack을 사용하여 텍스트 필드, 버튼, 리스트를 배치합니다.
- TextField("Product name", text: $name)
- TextField("Product quantity", text: $quantity)
- 두 개의 텍스트 필드는 제품의 이름과 수량을 입력받습니다.
4. 버튼 및 기능
- Button("Add") { addProduct() }
- NavigationLink(destination: ResultsView(name: name, viewContext: viewContext)) { Text("Find") }
- Button("Clear") { name = ""; quantity = "" }
- Add 버튼은 addProduct 메서드를 호출하여 새로운 제품을 CoreData에 추가합니다.
- Find 버튼은 ResultsView로 네비게이트하여 검색 결과를 보여줍니다.
- Clear 버튼은 입력 필드를 초기화합니다.
5. 리스트
- List { ForEach(products) { product in ... } }
제품 목록을 표시합니다. 제품을 삭제하는 기능도 포함됩니다.
6. 함수
- addProduct()
- 새로운 Product 객체를 생성하고 입력된 이름과 수량을 설정한 후 saveContext 메서드를 호출하여 저장합니다.
- deleteProducts(offsets: IndexSet)
- 선택된 제품을 삭제한 후 saveContext 메서드를 호출합니다.
- saveContext()
- 변경된 내용을 저장하고, 에러가 발생하면 처리합니다.
ResultsView는 특정 이름을 포함하는 제품을 검색하고 결과를 표시하는 화면입니다.
7. 데이터 가져오기
- .task { ... }
뷰가 로드될 때 NSPredicate를 사용하여 제품 이름이 검색 조건과 일치하는 항목을 가져옵니다.
오늘은 여기까지 :)
감사합니다.
잘못된 내용이 있거나 더 좋은 내용 피드백은 언제나 환영합니다!
궁금하신 부분은 댓글로 질문 부탁드립니다!
'Apple > SwiftUI' 카테고리의 다른 글
[SwiftUI] Property Wrapper 알아보기 (0) | 2024.07.29 |
---|---|
[SwiftUI] AVFoundation 톺아보기 (2) | 2024.07.15 |
[SwiftUI] ObservedObject가 뭘까?? (0) | 2024.06.06 |
[SwiftUI] @State란 무엇일까? (2) | 2024.05.27 |
[SwiftUI] List 동적 리스트로 구현하기 (2) | 2024.05.15 |