- 영상 : Build an app with SwiftData
- 이전글 : SwiftData 알아보기 (1)
안녕하세요! 오늘부터 닉네임을 바꾼 구 비비(BIBI) 현 토브(Tov)입니다.
iOS 개발자 커뮤니티에서 활동하시는 `비비`분들이 저 포함 4명이더라구요..
'흔들리는 비비들속에서 내 샴푸향 ... 느낄 수 있을까 ...?'
?? : 아니
... 그래서 바꿨습니다...
...네
각설하고 이전글에 이어 SwiftData를 마저 탐방해보겠습니다!
이번엔 SwiftUI 환경에서 앱을 빌드하는 방법에 대해 설명해볼게요!
Apple에서 제공하는 샘플코드와 함께 살펴볼게요!
SwiftData를 사용하여 카드 덱 데이터를 유지하고, 카드를 터치할 때마다 앞면과 뒷면을 Filp 할 수 있는 간단한 단어장 앱이에요!
들어가기에 앞서 SwiftData를 적용하는데에 딱 3가지 매크로를 기억하면 됩니다!
이외에는 아래 개념도 같이 다뤄볼게요!
@Model
& @Bindable
& @Query
먼저 아주 간단하게 모델 객체를 만들어볼게요!
파일명 : Card.swift
import SwiftData
@Model
이 2가지만 하면됩니다.
// BEFORE Not using SwiftData
import Foundation
final class Card: ObservableObject {
@Published var front: String
@Published var back: String
var creationDate: Date
// ...
}
// AFTER: Using SwiftData
import Foundation
import SwiftData
@Model
final class Card {
var front: String
var back: String
var creationDate: Date
// ...
}
위와같이 @Model
매크로를 사용하면 Card가 Observable
프로토콜을 준수하게 되므로 ObservableObject
로 사용할 수 있습니다. 따라서 Observable
및 @Published
속성 래퍼를 사용할 필요가 없어집니다.
@Observable
새롭게 업데이트된 @Observable
매크로로 인해 조금 더 쉽게 데이터 바인딩 방식이 바뀌었다고 하네요! ObservedObject
속성 래퍼를 Bindable
로 변경하는것만으로 적용된 걸 확인할 수 있었습니다!
이와 관련된 세션이 따로 있어서 보긴 했지만, 직접적으로 SwiftData 와의 관련 여부를 설명해 주진 않았던 것으로 기억해요. 하지만 @Binable
을 채택하여 바인딩 하는 걸로 보아서는 @Model
매크로 안에 @Observable
매크로가 포함되어 있을 수도 있겠다고 추측됩니다. 개인적인 견해일 뿐입니다!ㅎㅎ
파일명 : CardEditorView.swift
import SwiftData
@ObservedObject
→@Bindable
어때요, 참 쉽죠?
// BEFORE Not using SwiftData
struct CardEditorView: View {
@ObservedObject var card: Card
// AFTER: Using SwiftData
struct CardEditorView: View {
@Bindable var card: Card
@Query를 적용하기 전에 간단하게 개념을 짚고 넘어가보겠습니다!
🌿 @Query
란?
Property wrapper
입니다.@State
가 수행하는 방식과 유사하게 모델이 변경될 때마다 업데이트된 뷰를 트리거합니다.@Query
프로퍼티를 가질 수 있습니다.@Query
는 모델 컨테이너에서 모델 컨텍스트를 가져와 데이터 소스로 사용합니다.@Query (sort: \.created) private var cards: [Card]
🙄 Sort 되긴 하는거야 ..?
갑자기 이런 궁금증이 생겨 알아보았는데요. 삽질이 조금 길어졌네요 😅
접기 기능을 이용하려고 했는데 velog는 적용이 안되네요...! 궁금하신 분들만 보세요 !
파일명 : ContentView.swift
import SwiftData
@State
→ @Query
// BEFORE Not using SwiftData
import SwiftUI
struct ContentView: View {
@State private var cards: [Card] = SampleDeck.contents
// ...
}
// AFTER: Using SwiftData
import SwiftUI
import SwiftData
struct ContentView: View {
@Query private var cards: [Card]
// ...
}
위와같이 @Query
를 통해 Observable
유형의 속성이 변경되면 데이터를 자동으로 View에 업데이트 됩니다.
우선 여기까지는 SwiftData와 관련된 매크로 3가지를 사용하는 방법에 대해서 소개했습니다! 이 아래부터는 모델 컨테이너
와 모델 컨텍스트
를 이용하여 실질적인 데이터 처리를 다뤄보겠습니다.
modelContainer
새로운 View Modifier입니다.
🌱 모델 컨테이너 설정하기
모델 컨테이너를 지정하지 않으면 뷰에서 데이터를 가져올 수 없습니다.
루트뷰에서 설정하는 경우 하위뷰에서 모델 컨텍스트를 이용해 데이터를 바인딩할 수 있습니다.
하위 뷰에서 설정할 경우 그보다 더 하위에 해당하는 뷰에 한하여 모델 컨테이너가 적용됩니다.
🌱 스토리지 스택 생성하기
.modelContainer(for: Card.self)
이 코드만 작성하면 하위 뷰에서 @Query
가 사용할 컨텍스트를 포함하여 전체 스토리지 스택을 생성합니다.
🌱 하나의 뷰는 하나 이상의 모델 컨테이너를 포함할 수 있습니다.
.modelContainer(for: [Trip.self, BucketListItem.self, LivingAccommodation.self])
새롭게 출시된 Swift의 Environment variable 입니다.
@Environment (\.modelContext) private var modelContext
이건 modelContainer를 지정할 때 자동으로 환경 변수가 설정된다고 하네요.
🌱 모델 컨텍스트에 대한 액세스를 제공합니다.
modelContext.뭐시기
로 접근할 수 있습니다.
let newCard = Card(front: "Sample Front", back: "Sample Back")
modelContext.insert(object: newCard)
🌱 원하는 만큼 가질 수 있습니다.
이것또한 modelContainer와 마찬가지로 각 View에는 단일 컨텍스트가 있지만,
일반적으로 Application은 필요한 만큼 많이 가질 수 있다고 합니다.
Model your schema with SwiftData세션에서 modelContainer를 배열형태로 선언하는 걸 봤는데 modelContext는 다중으로 어떻게 사용하는건지 아직 못봤네요! 다음 글에서 설명할 수 있게 찾아보겠습니다!
특별한점은 CoreData
에서 save()
를 작동시켜야만 저장되었던것과 달리,
insert, update, delete 와 같은 UI 관련 이벤트에 의해 자동 저장 트리거가 발생합니다! 편리하네요 😀
import SwiftData
@MainActor
let previewContainer: ModelContainer = {
do {
let container = try ModelContainer(
for: Card.self, ModelConfiguration(inMemory: true)
)
for card in SampleDeck.contents {
container.mainContext.insert(object: card)
}
return container
} catch {
fatalError("Failed to create container")
}
}()
@MainActor
매크로를 통해 프리뷰에서 보여줄 ModelContainer 를 정의합니다.
매크로에 대해서는 Write Swift macros 세션을 통해서 깊이 공부해봐야겠습니다.
Apple이 미리 만들어놓은 샘플 데이터는 아래와 같았습니다.
#Preview {
ContentView()
.frame(minWidth: 500, minHeight: 500)
.modelContainer(previewContainer)
}
위에서 정의한 previewContainer를 불러오기만 프리뷰에서 볼 수 있게됩니다!
하지~만? 버그인지 현재 해당 코드는 프리뷰가 작동하지 않아서 피드백 넣어놓은 상태입니다…🥲
SwiftData를 통해 간편하게 문서 기반 앱을 개발 할 수 있게되었습니다.
문서 기반 앱은 사용자가 다양한 유형의 문서를 작성, 열기, 보기, 편집할 수 있는 애플리케이션입니다.
예를 들어 pages, keynote 와 같이 하나의 파일 형태로 FlashCard 묶음을 저장할 수 있게 된다는거죠!
문서 기반 앱을 설정하기 위해 DocumentGroup 이니셜라이저를 사용하여 앱을 해당 형식의 앱으로 전환합니다. 모델 유형(Card.self), 콘텐츠 유형 및 뷰 빌더를 전달합니다.
import SwiftUI
import SwiftData
@main
struct SwiftDataFlashCardSample: App {
var body: some Scene {
#if os(iOS) || os(macOS)
DocumentGroup(editing: Card.self, contentType: .flashCards) {
ContentView()
}
#else
WindowGroup {
ContentView()
.modelContainer(for: Card.self)
}
#endif
}
}
contentType은 SwiftData 문서의 고유한 표현을 나타내는 데 사용됩니다. 이는 파일 확장자와 연결됩니다.
잠깐 ! 여기서 콘텐츠 유형이란 크게 2가지로 나누어 볼 수 있는데
JPEG 와 같은 Binary data document
와 Xcode 프로젝트 파일 같은 Package document
가 있습니다.
SwiftData 문서는 패키지 형식
이며, 외부에 표시된 속성은 문서 패키지의 일부가 됩니다.
import UniformTypeIdentifiers
extension UTType {
static var flashCards = UTType(exportedAs: "com.example.flashCards")
}
UTType을 init 할 때 exportedAs 파라미터에는 앱의 identifier String을 넣어주면 됩니다.
그리고 info.plist - Exported Type Identifiers 를 아래와 같이 설정합니다.
*이때 extension에서 작성한 identifier와 String이 일치하지 않으면 에러가 발생할 수 있습니다.
설정을 마치면
.sampledeck
이라는 확장자를 가진 패키지 형식의 Flash Cards Deck 파일을 따로 저장할 수 있게됩니다.
설명이 좀 길었지만, 생각보다 SwiftData를 사용하는 것 자체는 쉽다는 생각을 했습니다!
하지만 아직 베타라서 그런지 WWDC 세션에서 제공된 프리뷰 코드나, sort 코드가 잘 작동하지 않아서 아쉬웠습니다.
포럼에도 프리뷰 오류에 대한 질문들이 있어서 적용해봤고 그것도 해결되진 않았습니다..ㅎㅎ
얼른 안정적으로 자리 잡아서 관계형 데이터를 이용한 앱을 조금 더 쉽게 만들어보고 싶네요!
긴 글 읽어주셔서 감사합니다..! 언제나 지적이나 질문 환영합니다!