SwiftUI 미리 보기와 함께 Realm 사용
이 페이지의 내용
개요
SwiftUI Preview는 개발에 유용한 도구입니다. SwiftUI Preview에서 Realm 데이터를 여러 방법으로 작업할 수 있습니다.
세부 정보 보기에서 사용할 개별 객체 초기화
대신 조건부로 객체 배열을 사용합니다.
@ObservedResults
미리 보기용 데이터가 포함된 영역 만들기
SwiftUI Preview 디버깅은 불투명할 수 있으므로 SwiftUI Preview 내에서 지속되는 Realms 관련 문제를 디버깅하기 위한 몇 가지 팁도 준비했습니다.
세부 정보 보기에 대한 객체 초기화
간단한 문제의 경우, 초기화 시 직접 설정할 수 있는 Realm 속성을 사용하는 하나 이상의 객체와 함께 SwiftUI Preview를 사용할 수 있습니다. 세부 정보 보기를 미리 볼 때 이 작업을 수행할 수 있습니다. DoggoDB의 DogDetailView
를 고려합니다:
struct DogDetailView: View { 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 사용
목록 보기에서 @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 확장에서 dogArray
를 static let
로 생성하고 이미 생성한 항목 객체를 포함하겠습니다.
static let dogArray = [dog1, dog2, dog3]
그런 다음 List를 반복할 때 Preview에서 실행 중인 경우 정적 dogArray
을 사용하고 Preview가 아닌 경우 @ObservedResults
쿼리를 사용합니다.
struct DogsView: View { var isPreview (\.isPreview) 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에서 사용하는 데이터로 영역을 생성할 수 있습니다.
미리 보기용 데이터로 Realm 만들기
경우에 따라 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) } }
인메모리 Realm 사용
가능하면 인메모리 영역을 사용하여 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가 충돌하게 됩니다.
SwiftUI Preview 삭제
마이그레이션이 필요하여 Preview에서 영역을 로드하지 못하는 등 상태와 관련된 다른 SwiftUI Preview 문제가 발생하는 경우 캐시된 Preview 데이터를 제거하기 위해 수행할 수 있는 몇 가지 방법이 있습니다.
Apple이 권장하는 수정 방법은 Xcode를 닫고 명령줄을 사용하여 기존의 모든 SwiftUI Preview 데이터를 삭제하는 것입니다.
Xcode를 닫습니다.
명령줄에서 실행합니다:
xcrun simctl --set previews delete all
이 명령을 실행한 후에도 데이터가 지속될 수 있습니다. 이는 Preview의 어떤 문제로 인해 Xcode가 참조를 유지하고 삭제할 수 없기 때문일 수 있습니다. 다음 단계를 시도하여 문제를 해결할 수도 있습니다:
다른 시뮬레이터를 위해 빌드
컴퓨터를 다시 시작하고 다시 실행
xcrun simctl --set previews delete all
저장된 Preview 데이터를 바로 삭제합니다. 이 데이터는
~/Library/Developer/Xcode/UserData/Previews
에 저장됩니다.
SwiftUI Preview 충돌에 대한 자세한 정보 확인
영역을 사용할 때 설명할 수 없는 SwiftUI 미리보기 충돌이 발생하는 경우 먼저 시뮬레이터에서 애플리케이션을 실행해 보세요. 시뮬레이터에 사용할 수 있는 오류 메시지와 로그는 문제를 찾고 진단하기 더 쉽게 해줍니다. 시뮬레이터에서 문제를 디버깅할 수 있다면 이 방법이 가장 쉬운 방법입니다.
시뮬레이터에서 SwiftUI Preview 충돌을 재현할 수 없는 경우, SwiftUI Preview 앱의 충돌 로그를 확인할 수 있습니다. 이 로그는 ~/Library/Logs/DiagnosticReports/
에서 확인할 수 있습니다. 해당 로그를 표시하는 데 약간의 지연이 있을 수 있으므로 관련 로그가 즉시 표시되지 않으면 충돌 후 몇 분 정도 기다립니다.
Preview에서 동기화된 Realm 사용
앱에서 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) } }