Docs Menu
Docs Home
/ /
Atlas Device SDK
/ /

SwiftUI 미리 보기와 함께 Realm 사용

이 페이지의 내용

  • 개요
  • 세부 정보 보기에 대한 객체 초기화
  • 목록 보기에서 조건부로 ObservedResults 사용
  • 미리 보기용 데이터로 Realm 만들기
  • 인메모리 Realm 사용
  • SwiftUI Preview 삭제
  • SwiftUI Preview 충돌에 대한 자세한 정보 확인
  • Preview에서 동기화된 Realm 사용

SwiftUI Preview는 개발에 유용한 도구입니다. SwiftUI Preview에서 Realm 데이터를 여러 방법으로 작업할 수 있습니다.

  • 세부 정보 보기에서 사용할 개별 객체 초기화

  • 대신 조건부로 객체 배열을 사용합니다. @ObservedResults

  • 미리 보기용 데이터가 포함된 영역 만들기

SwiftUI Preview 디버깅은 불투명할 수 있으므로 SwiftUI Preview 내에서 지속되는 Realms 관련 문제를 디버깅하기 위한 몇 가지 팁도 준비했습니다.

간단한 문제의 경우, 초기화 시 직접 설정할 수 있는 Realm 속성을 사용하는 하나 이상의 객체와 함께 SwiftUI Preview를 사용할 수 있습니다. 세부 정보 보기를 미리 볼 때 이 작업을 수행할 수 있습니다. DoggoDB의 DogDetailView를 고려합니다:

struct DogDetailView: View {
@ObservedRealmObject var dog: Dog
var body: some View {
VStack {
Text(dog.name)
.font(.title2)
Text("\(dog.name) is a \(dog.breed)")
AsyncImage(url: dog.profileImageUrl) { image in
image.resizable()
} placeholder: {
ProgressView()
}
.aspectRatio(contentMode: .fit)
.frame(width: 150, height: 150)
Text("Favorite toy: \(dog.favoriteToy)")
}
}
}

모델 객체에 대한 확장자를 만듭니다. 이 확장 기능을 어디에 배치할지는 코드베이스의 규칙에 따라 달라집니다. 모델 파일에 직접 넣거나, 샘플 데이터를 위한 전용 디렉터리를 만들거나, 코드베이스 내 다른 규칙을 사용할 수 있습니다.

이 확장에서는 static let 사용하여 하나 이상의 Realm 객체를 초기화합니다.

extension Dog {
static let dog1 = Dog(value: ["name": "Lita", "breed": "Lab mix", "weight": 27, "favoriteToy": "Squeaky duck", "profileImageUrl": "https://www.corporaterunaways.com/images/2021/04/lita-768x768.jpeg"])
static let dog2 = Dog(value: ["name": "Maui", "breed": "English Springer Spaniel", "weight": 42, "favoriteToy": "Wubba", "profileImageUrl": "https://www.corporaterunaways.com/images/2021/04/maui_with_leaf-768x576.jpeg"])
static let dog3 = Dog(value: ["name": "Ben", "breed": "Border Collie mix", "weight": 48, "favoriteToy": "Frisbee", "profileImageUrl": "https://www.corporaterunaways.com/images/2012/03/ben-630x420.jpg"])
}

이 예시 에서는 값을 사용하여 객체를 초기화합니다. 모델에 직접 초기화할 수 있는 속성이 포함된 경우에만 값을 사용하여 객체를 초기화할 수 있습니다. 모델 객체 에 List 속성 과 같이 쓰기 트랜잭션( 쓰기 트랜잭션 (write transaction) ) 내에서만 변경할 수 있는 속성이 포함되어 있는 경우 대신영역 Preview와 함께 사용할 SwiftUI 을 만들어야 합니다.

모델 클래스의 확장으로 객체를 초기화한 후 이를 SwiftUI Preview에서 사용할 수 있습니다. Preview에서 객체를 View로 바로 전달할 수 있습니다:

struct DogDetailView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
DogDetailView(dog: Dog.dog1)
}
}
}

목록 보기에서 @ObservedResults 를 사용하면 암시적으로 영역 이 열리고 쿼리됩니다. Preview에서 이 기능을 사용하려면 데이터로 채워진 영역 이 필요합니다. 또는 Preview에서 조건부로 정적 배열 을 사용하고 앱 을 실행 때 @ObservedResults 변수만 사용할 수 있습니다.

여러 가지 방법이 있지만, 코드를 더 쉽게 읽고 이해할 수 있도록 Preview에서 앱이 실행되고 있는지 여부를 감지할 수 있는 EnvironmentValue 를 만드는 것이 좋습니다.

import Foundation
import SwiftUI
public extension EnvironmentValues {
var isPreview: Bool {
#if DEBUG
return ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
#else
return false
#endif
}
}

그런 다음 이를 View의 환경 값으로 사용할 수 있으며 Preview 상태인지 여부에 따라 사용하는 변수를 조건부로 변경할 수 있습니다.

이 예시 는 위에서 정의한 Dog 확장을 기반으로 합니다. Dog 확장에서 dogArraystatic let 로 생성하고 이미 생성한 항목 객체를 포함하겠습니다.

static let dogArray = [dog1, dog2, dog3]

그런 다음 List를 반복할 때 Preview에서 실행 중인 경우 정적 dogArray 을 사용하고 Preview가 아닌 경우 @ObservedResults 쿼리를 사용합니다.

struct DogsView: View {
@Environment(\.isPreview) var isPreview
@ObservedResults(Dog.self) var dogs
var previewDogs = Dog.dogArray
var body: some View {
NavigationView {
VStack {
List {
if isPreview {
ForEach(previewDogs) { dog in
DogRow(dog: dog)
}
} else {
ForEach(dogs) { dog in
DogRow(dog: dog)
}.onDelete(perform: $dogs.remove)
}
}
... More View code

이렇게 하면 가볍고 데이터를 유지하지 않는다는 장점이 있지만 View 코드가 더욱 장황해질 수 있다는 단점이 있습니다. 더 깔끔한 View 코드를 선호하는 경우 Previews에서 사용하는 데이터로 영역을 생성할 수 있습니다.

경우에 따라 SwiftUI Preview에서 영역 데이터를 볼 수 있는 유일한 옵션은 데이터가 포함된 영역을 만드는 것입니다. List MutableSet 같이 값으로 직접 초기화하지 않고 쓰기 트랜잭션(write transaction) 중에만 채울 수 있는 속성을 채울 때 이 작업을 수행할 수 있습니다. 뷰에서 다른 뷰에서 전달되는 더 복잡한 객체 계층 구조를 사용하는 경우에도 이 작업을 수행하는 것이 좋습니다.

그러나 영역을 직접 사용하는 것은 SwiftUI 미리보기에 상태를 삽입하게 되며, 이는 일부 단점을 가져올 수 있습니다. Realm이든 Core Data이든, 상태를 가진 SwiftUI 미리보기는 다양한 문제를 일으킬 수 있습니다.

  • 영역 파일 생성 단계를 반복적으로 다시 실행하여 예상치 못한 데이터 또는 중복된 데이터가 표시됨

  • 모델을 변경할 때 SwiftUI Preview 내에서 마이그레이션을 수행해야 하는 문제

  • 뷰 내 상태 변경과 관련된 잠재적 문제

  • SwiftUI Previews 내 눈에 보이지 않는 문제와 관련된 설명할 수 없는 충돌 또는 성능 문제

다음 팁을 통해 이러한 문제를 방지하거나 해결할 수 있습니다:

모델 확장에서 Realm에 대한 정적 변수를 만들 수 있습니다. 여기에서 Realm을 채우기 위한 작업을 수행합니다. 이 경우에는 Person 을 생성하고 dogs 목록 속성에 Dog 객체 몇 개를 추가합니다. 이 예제 는 Dog 확장 프로그램에서 몇 개의 Dog 객체를 초기화한 위의 예제를 기반으로 합니다.

Person 확장자를 만들고 해당 확장자에 단일 Person 객체를 만듭니다. 그런 다음 방금 생성한 Person을 추가하고 Dog 확장자의 예시 Dog 객체를 추가하여 previewRealm을 생성합니다.

이러한 객체를 두 번 이상 추가하는 것을 방지하기 위해 Person 객체를 쿼리하고 그 수가 1인지 확인하여 Person이 이미 존재하는지 확인하는 검사를 추가합니다. 영역에 Person이 포함되어 있으면 SwiftUI Preview에서 사용할 수 있습니다. 그렇지 않은 경우 데이터를 추가합니다.

static var previewRealm: Realm {
var realm: Realm
let identifier = "previewRealm"
let config = Realm.Configuration(inMemoryIdentifier: identifier)
do {
realm = try Realm(configuration: config)
// Check to see whether the in-memory realm already contains a Person.
// If it does, we'll just return the existing realm.
// If it doesn't, we'll add a Person append the Dogs.
let realmObjects = realm.objects(Person.self)
if realmObjects.count == 1 {
return realm
} else {
try realm.write {
realm.add(person)
person.dogs.append(objectsIn: [Dog.dog1, Dog.dog2, Dog.dog3])
}
return realm
}
} catch let error {
fatalError("Can't bootstrap item data: \(error.localizedDescription)")
}
}

SwiftUI Preview에서 이를 사용하려면 ProfileView 코드에 프로필이 필요합니다. 이것은 Person 객체의 프로젝션 입니다. Preview에서는 영역을 가져와서 프로필에 대해 쿼리하고 View에 전달할 수 있습니다.

struct ProfileView_Previews: PreviewProvider {
static var previews: some View {
let realm = Person.previewRealm
let profile = realm.objects(Profile.self)
ProfileView(profile: profile.first!)
}
}

영역 객체가 전달될 것으로 예상되는 View가 없지만 대신 @ObservedResults 을 사용하여 영역을 쿼리하거나 기존 영역으로 작업하는 경우 영역을 환경 값으로 View에 삽입할수 있습니다:

struct SomeListView_Previews: PreviewProvider {
static var previews: some View {
SomeListView()
.environment(\.realm, Person.previewRealm)
}
}

가능하면 인메모리 영역을 사용하여 SwiftUI Preview 내에서 데이터베이스를 사용할 때 발생할 수 있는 일부 상태 관련 문제를 해결하세요.

영역을 초기화할 때 inMemoryIdentifier 구성 속성을 사용하세요.

static var previewRealm: Realm {
var realm: Realm
let identifier = "previewRealm"
let config = Realm.Configuration(inMemoryIdentifier: identifier)
do {
realm = try Realm(configuration: config)
// ... Add data to realm

참고

SwiftUI Preview를 위해 영역을 초기화할 때 deleteRealmIfMigrationNeeded 설정 속성을 사용하지 마세요. Apple이 SwiftUI Preview를 구현한 방식 때문에 이 속성을 사용하여 마이그레이션 문제를 우회하면 SwiftUI Preview가 충돌하게 됩니다.

마이그레이션이 필요하여 Preview에서 영역을 로드하지 못하는 등 상태와 관련된 다른 SwiftUI Preview 문제가 발생하는 경우 캐시된 Preview 데이터를 제거하기 위해 수행할 수 있는 몇 가지 방법이 있습니다.

Apple이 권장하는 수정 방법은 Xcode를 닫고 명령줄을 사용하여 기존의 모든 SwiftUI Preview 데이터를 삭제하는 것입니다.

  1. Xcode를 닫습니다.

  2. 명령줄에서 실행합니다:

    xcrun simctl --set previews delete all

이 명령을 실행한 후에도 데이터가 지속될 수 있습니다. 이는 Preview의 어떤 문제로 인해 Xcode가 참조를 유지하고 삭제할 수 없기 때문일 수 있습니다. 다음 단계를 시도하여 문제를 해결할 수도 있습니다:

  • 다른 시뮬레이터를 위해 빌드

  • 컴퓨터를 다시 시작하고 다시 실행 xcrun simctl --set previews delete all

  • 저장된 Preview 데이터를 바로 삭제합니다. 이 데이터는 ~/Library/Developer/Xcode/UserData/Previews에 저장됩니다.

영역을 사용할 때 설명할 수 없는 SwiftUI 미리보기 충돌이 발생하는 경우 먼저 시뮬레이터에서 애플리케이션을 실행해 보세요. 시뮬레이터에 사용할 수 있는 오류 메시지와 로그는 문제를 찾고 진단하기 더 쉽게 해줍니다. 시뮬레이터에서 문제를 디버깅할 수 있다면 이 방법이 가장 쉬운 방법입니다.

시뮬레이터에서 SwiftUI Preview 충돌을 재현할 수 없는 경우, SwiftUI Preview 앱의 충돌 로그를 확인할 수 있습니다. 이 로그는 ~/Library/Logs/DiagnosticReports/에서 확인할 수 있습니다. 해당 로그를 표시하는 데 약간의 지연이 있을 수 있으므로 관련 로그가 즉시 표시되지 않으면 충돌 후 몇 분 정도 기다립니다.

앱에서 Atlas Device Sync를 사용하는 경우 SwiftUI Preview에서 동기화된 영역을 사용하는 방법이 궁금할 수 있습니다. 더 좋은 방법은 SwiftUI Preview용 데이터로 채운 정적 객체나 로컬 영역을 사용하는 것입니다.

예시 앱에서는 영역을 전혀 사용하지 않고도 기기 동기화와 Device Sync - 로그인 보기를 미리 볼 수 있습니다:

struct LoginView_Previews: PreviewProvider {
static var previews: some View {
LoginView()
}
}

정적 UI만 보기 때문에 LoginView 를 표시할지 OpenSyncedRealmView로 이동할지에 대한 로직이 포함된 SyncContentView에 대해서는 걱정할 필요가 없습니다. OpenSyncedRealmView의 미리보기를 건너뛸 수도 있습니다. 이 View는 동기화된 영역을 열고 DogsView에 데이터를 채우는 로직만 처리하기 때문입니다. 따라서 Preview에서 볼 다음 View는 DogsView입니다.

다행히 Realm을 사용하면 Realm에서 작업하는 코드는 Device Sync를 사용하는지 여부에 관계없이 동일한 방식으로 작업합니다. 따라서 위의 예제에서 생성 한 것과 동일한 로컬 Realm을 SwiftUI Preview에서 사용할 수 있습니다.

struct DogsView_Previews: PreviewProvider {
static var previews: some View {
let realm = Person.previewRealm
DogsView()
.environment(\.realm, realm)
}
}

돌아가기

백그라운드에서 데이터 동기화