SwiftData 알아보기 (2)

doyeonjeong_·2023년 6월 28일
1

WWDC

목록 보기
4/4
post-thumbnail

안녕하세요! 오늘부터 닉네임을 바꾼 구 비비(BIBI) 현 토브(Tov)입니다.


iOS 개발자 커뮤니티에서 활동하시는 `비비`분들이 저 포함 4명이더라구요..
'흔들리는 비비들속에서 내 샴푸향 ... 느낄 수 있을까 ...?'

?? : 아니

... 그래서 바꿨습니다...

...네




각설하고 이전글에 이어 SwiftData를 마저 탐방해보겠습니다!
이번엔 SwiftUI 환경에서 앱을 빌드하는 방법에 대해 설명해볼게요!


🌲 Meet the app : FlashCard

Apple에서 제공하는 샘플코드와 함께 살펴볼게요!
SwiftData를 사용하여 카드 덱 데이터를 유지하고, 카드를 터치할 때마다 앞면과 뒷면을 Filp 할 수 있는 간단한 단어장 앱이에요!


🖐🏻 잠깐!

들어가기에 앞서 SwiftData를 적용하는데에 딱 3가지 매크로를 기억하면 됩니다!

  1. @Model
  2. @Binable
  3. @Query

이외에는 아래 개념도 같이 다뤄볼게요!

  • ModelContainer
  • ModelContext

🌲 @Model & @Bindable & @Query

먼저 아주 간단하게 모델 객체를 만들어볼게요!

파일명 : Card.swift

  1. import SwiftData
  2. @Model

이 2가지만 하면됩니다.

🪄 @Model 적용 코드

// 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

  1. import SwiftData
  2. @ObservedObject@Bindable

어때요, 참 쉽죠?

🪄 @Bindable 적용 코드

// BEFORE Not using SwiftData
struct CardEditorView: View {
    @ObservedObject var card: Card
// AFTER: Using SwiftData
struct CardEditorView: View {
    @Bindable var card: Card

@Query를 적용하기 전에 간단하게 개념을 짚고 넘어가보겠습니다!

🌿 @Query 란?

  • SwiftData에서 모델을 쿼리하는 새로운 Property wrapper 입니다.
  • @State가 수행하는 방식과 유사하게 모델이 변경될 때마다 업데이트된 뷰를 트리거합니다.
  • 모든 View는 필요한 만큼 @Query 프로퍼티를 가질 수 있습니다.
  • @Query는 모델 컨테이너에서 모델 컨텍스트를 가져와 데이터 소스로 사용합니다.
  • @Query는 정렬, 정렬, 필터링 및 애니메이션 변경을 구성하는 간단한 구문을 제공합니다.
@Query (sort: \.created) private var cards: [Card]

🙄 Sort 되긴 하는거야 ..?
갑자기 이런 궁금증이 생겨 알아보았는데요. 삽질이 조금 길어졌네요 😅
접기 기능을 이용하려고 했는데 velog는 적용이 안되네요...! 궁금하신 분들만 보세요 !


🪄 @Query 적용 코드

파일명 : ContentView.swift

  1. import SwiftData
  2. @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 & ModelContext & Preview

🌿 modelContainer

새로운 View Modifier입니다.

🌱 모델 컨테이너 설정하기

모델 컨테이너를 지정하지 않으면 뷰에서 데이터를 가져올 수 없습니다.

루트뷰에서 설정하는 경우 하위뷰에서 모델 컨텍스트를 이용해 데이터를 바인딩할 수 있습니다.

하위 뷰에서 설정할 경우 그보다 더 하위에 해당하는 뷰에 한하여 모델 컨테이너가 적용됩니다.

🌱 스토리지 스택 생성하기

.modelContainer(for: Card.self)

이 코드만 작성하면 하위 뷰에서 @Query가 사용할 컨텍스트를 포함하여 전체 스토리지 스택을 생성합니다.

🌱 하나의 뷰는 하나 이상의 모델 컨테이너를 포함할 수 있습니다.

.modelContainer(for: [Trip.self, BucketListItem.self, LivingAccommodation.self])

🌿 modelContext

새롭게 출시된 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 관련 이벤트에 의해 자동 저장 트리거가 발생합니다! 편리하네요 😀


🌿 Preview에 DummyData 띄우기

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를 불러오기만 프리뷰에서 볼 수 있게됩니다!

하지~만? 버그인지 현재 해당 코드는 프리뷰가 작동하지 않아서 피드백 넣어놓은 상태입니다…🥲


🌲 [Bonus] Document-based apps

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 코드가 잘 작동하지 않아서 아쉬웠습니다.
포럼에도 프리뷰 오류에 대한 질문들이 있어서 적용해봤고 그것도 해결되진 않았습니다..ㅎㅎ
얼른 안정적으로 자리 잡아서 관계형 데이터를 이용한 앱을 조금 더 쉽게 만들어보고 싶네요!

긴 글 읽어주셔서 감사합니다..! 언제나 지적이나 질문 환영합니다!

profile
블로그 이사중 🚚 byukbyak.tistory.com

0개의 댓글