Docs 菜单
Docs 主页
/ /
Atlas Device SDKs
/ /

使用 Realm 和 SwiftUI 预览

在此页面上

  • Overview
  • 初始化详细视图的对象
  • 有条件地在列表视图中使用 observedResults
  • 使用预览数据创建 Realm
  • 在内存中使用 Realm
  • 删除 SwiftUI 预览版
  • 获取有关 SwiftUI 预览版崩溃的详细信息
  • 在预览中使用同步 Realm

SwiftUI 预览是开发过程中非常有用的工具。您可以通过以下几种方式在 SwiftUI 预览中使用 Realm 数据:

  • 初始化各个对象以在详细视图中使用

  • 有条件地使用对象数组代替 @ObservedResults

  • 创建包含预览数据的 Realm

SwiftUI Preview 调试可能比较难理解,因此我们还有一些关于在 SwiftUI Preview 中调试持久化 Realm 问题的提示。

在最简单的情况下,您可以将 SwiftUI 预览与一个或多个对象一起使用,这些对象使用可在初始化时直接设置的 Realm 属性。您可能希望在预览详细视图时执行此操作。考虑 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"])
}

在此示例中,我们使用一个值来初始化对象。 仅当模型包含可直接初始化的属性时,才能使用值初始化对象。 如果您的模型对象包含仅在写事务(write transaction)中可变的属性(例如List属性),则必须改为创建一个域来与SwiftUI预览一起使用。

将对象初始化为模型类的扩展后,您就可以在 SwiftUI 预览中加以使用。您可以将对象直接传递给预览中的视图:

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

在列表视图中使用@ObservedResults时,这会隐式打开一个域并对其进行查询。 为此,在预览版中,您需要一个由 data 填充的域 。 作为替代方案,您可以在预览中有条件地使用静态大量,并仅在运行应用时使用@ObservedResults变量。

您可以通过多种方式实现这一目的,但为了使代码更易于阅读和理解,我们将创建 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
}
}

然后,可以将其用作视图中的环境值,并根据我们是否处于预览状态有条件地更改使用的变量。

此示例基于我们上面定义的 Dog 扩展而构建。 我们将在 Dog 扩展中创建一个dogArray作为static let ,并包含已创建的列项对象:

static let dogArray = [dog1, dog2, dog3]

然后,当我们遍历列表时,如果是在预览中运行,则使用静态 dogArray,如果不是在预览中运行,则使用 @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

这样做的好处是轻量,不需要持久化任何数据,但缺点是会使视图代码变得更加冗长。如果您喜欢更简洁的视图代码,则可以用预览中使用的数据创建 Realm。

在某些情况下,在 SwiftUI 预览版中查看 Realm 数据的唯一选择是创建包含数据的 Realm。在填充只能在写事务期间填充的属性时,可以执行此操作,而不是直接使用值(例如列表MutableSet)进行初始化。如果视图依赖于从其他视图传入的更复杂的对象层次结构,您可能还需要执行此操作。

不过,直接使用 Realm 会将状态注入 SwiftUI 预览,而这可能带来一些弊端。无论您使用的是 Realm 还是 CoreData,有状态的 SwiftUI 预览都可能导致以下问题:

  • 由于重复运行 Realm 文件创建步骤,导致出现意外或重复数据

  • 更改模型时需要在“SwiftUI 预览”中执行迁移

  • 与在视图内更改状态有关的潜在问题

  • 与“SwiftUI 预览”中未以可见方式显示的问题相关的无法解释的崩溃或性能问题

您可以通过以下提示来避免或解决其中一些问题:

您可以在模型扩展中为 Realm 创建静态变量。 您可以在此处进行 Realm 填充工作。 在我们的示例中,我们创建一个Person并将一些Dog对象附加到dogs List 属性。 此示例以上面的示例为基础,在上面的示例中,我们在 Dog 扩展中初始化了一些 Dog 对象。

我们将创建一个 Person 扩展,并在该扩展中创建一个 Person 对象。然后,我们将通过添加刚刚创建的 Person 并附加 Dog 扩展中的示例 Dog 对象来创建 previewRealm

为了避免多次添加这些对象,我们添加了一个检查功能,通过查询 Person 对象并检查计数是否为 1 来查看 Person 是否已存在。如果 Realm 包含 Person,我们就可以在 SwiftUI 预览版中使用它。如果没有,我们就添加数据。

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 预览版中使用它,ProfileView 代码需要一个配置文件。这是 Person 对象的投影。在预览版中,我们可以获取 Realm,查询它的配置文件,并将其传递给视图:

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

如果您没有需要传入 Realm 对象的视图,而是使用 @ObservedResults 来查询 Realm 或以其他方式处理现有 Realm,则可以将该 Realm 作为环境值注入到视图中:

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

在可能的情况下,使用内存中的 Realm 来解决在“SwiftUI 预览”中使用数据库可能产生的一些与状态相关的问题。

在初始化域时使用 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 预览”初始化域时,请勿使用 delete域IfMigrationNeeded 配置属性。由于 Apple 实现“SwiftUI 预览”的方式,使用此属性绕过迁移问题会导致“SwiftUI 预览”崩溃。

如果您遇到其他与状态有关的“SwiftUI 预览”问题,例如由于需要迁移而无法在“预览”中加载 Realm,您可以执行一些操作来删除缓存的“预览”数据。

Apple 建议的修复方法是关闭 Xcode,然后使用命令行删除所有现有的“SwiftUI 预览”数据。

  1. 关闭 Xcode。

  2. 在命令行中运行:

    xcrun simctl --set previews delete all

运行此命令后,数据可能会持久化。这可能是由于 Xcode 由于 Preview 中的某些内容而保留了引用并且无法删除它。您还可以尝试以下步骤来解决问题:

  • 为不同的模拟器构建

  • 重新启动计算机并重新运行 xcrun simctl --set previews delete all

  • 直接删除已存储的预览数据。此数据存储在 ~/Library/Developer/Xcode/UserData/Previews 中。

如果在使用 Realm 时遇到不明原因的 SwiftUI 预览版崩溃,请首先尝试在模拟器上运行应用程序。模拟器提供的错误消息和日志使查找和诊断问题变得更容易。如果可以在模拟器中调试问题,这是最简单的方法。

如果无法在模拟器中复制 SwiftUI Preview 崩溃,您可以查看 SwiftUI Preview 应用程序的崩溃日志。可在 ~/Library/Logs/DiagnosticReports/ 中找到这些日志。这些日志有时会延迟出现,因此如果没有立即看到相关日志,请在崩溃后等待几分钟。

如果您的应用程序使用 Atlas Device Sync,您可能会想知道如何在“SwiftUI 预览”中使用同步的 Realm。更好的做法是使用静态对象或本地 Realm,为“SwiftUI 预览”填充数据。

在我们的示例应用程序中,我们可以预览与 Device Sync 相关的视图 LoginView,而根本不需要使用 Realm:

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

由于我们只查看静态用户界面,因此无需担心 SyncContentView 是否包含显示 LoginView 或转到 OpenSyncedRealmView 的逻辑。我们还可以跳过预览 OpenSyncedRealmView,因为其只是处理与打开同步 Realm 和为 DogsView 填充相关的逻辑。因此我们想在“预览”中查看的下一个视图是 DogsView。

幸运的是,使用 Realm 时,使用 Realm 的代码并不关心 Realm 是否使用 Device Sync,您可以以相同的方式使用 Realm。 因此,我们可以在 SwiftUI 预览版中使用与上述示例中创建的相同的本地 Realm。

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

后退

在后台同步数据