数据持久化方案解析(十五) —— 基于Realm和SwiftUI的数据持久化简单示例(一)

版本记录

版本号 时间
V1.0 2020.10.19 星期一

前言

数据的持久化存储是移动端不可避免的一个问题,很多时候的业务逻辑都需要我们进行本地化存储解决和完成,我们可以采用很多持久化存储方案,比如说plist文件(属性列表)、preference(偏好设置)、NSKeyedArchiver(归档)、SQLite 3CoreData,这里基本上我们都用过。这几种方案各有优缺点,其中,CoreData是苹果极力推荐我们使用的一种方式,我已经将它分离出去一个专题进行说明讲解。这个专题主要就是针对另外几种数据持久化存储方案而设立。
1. 数据持久化方案解析(一) —— 一个简单的基于SQLite持久化方案示例(一)
2. 数据持久化方案解析(二) —— 一个简单的基于SQLite持久化方案示例(二)
3. 数据持久化方案解析(三) —— 基于NSCoding的持久化存储(一)
4. 数据持久化方案解析(四) —— 基于NSCoding的持久化存储(二)
5. 数据持久化方案解析(五) —— 基于Realm的持久化存储(一)
6. 数据持久化方案解析(六) —— 基于Realm的持久化存储(二)
7. 数据持久化方案解析(七) —— 基于Realm的持久化存储(三)
8. 数据持久化方案解析(八) —— UIDocument的数据存储(一)
9. 数据持久化方案解析(九) —— UIDocument的数据存储(二)
10. 数据持久化方案解析(十) —— UIDocument的数据存储(三)
11. 数据持久化方案解析(十一) —— 基于Core Data 和 SwiftUI的数据存储示例(一)
12. 数据持久化方案解析(十二) —— 基于Core Data 和 SwiftUI的数据存储示例(二)
13. 数据持久化方案解析(十三) —— 基于Unit Testing的Core Data测试(一)
14. 数据持久化方案解析(十四) —— 基于Unit Testing的Core Data测试(二)

开始

首先看下主要内容:

在本SwiftUI Realm教程中,您将通过构建购物清单应用程序来学习如何将RealmSwiftUI一起用作数据持久性解决方案。内容来自翻译

下面看下写作环境:

Swift 5, iOS 14, Xcode 12

接着就是正文啦

Realm Mobile Database是一种流行的对象数据库管理系统。它是开源的,可以在多个平台上使用。 Realm的目标是成为一种持久,快速,高性能,灵活和简单的解决方案,同时仍可编写类型安全的Swift代码。

SwiftUI是Apple最新,最热门的UI框架。它使用声明性语法来使用Swift代码构建视图。当用户与其交互时,它依靠状态states来被动地更新其视图。由于Realm使用的Live Objects也会自动更新,因此混合使用这两个框架很有意义!

在本SwiftUI领域教程中,您将学习如何:

  • 设置Realm
  • 定义数据模型
  • 对对象执行基本的CRUD操作
  • 将更改从数据库转到用户界面
  • 数据模型更改时处理迁移

您将通过在应用程序中实现Realm数据库来学习所有这些知识。

注意:本SwiftUI Realm教程假定您已经熟悉SwiftUI。如果您刚刚开始使用SwiftUI,请查看我们的SwiftUI video course

打开启动文件夹中的PotionsMaster.xcodeproj

PotionsMaster是一款可满足您所有制药需求的应用程序。 药水冲泡是一项具有挑战性的技能。 搅拌技术,时间安排和装瓶可能是艰巨的任务,即使对于经验丰富的技师也是如此。 这个应用可协助您追踪所需的食材和已购买的食材。 使用PotionsMaster,即使您一直在阅读的那个困难的新药也将变得轻而易举!

现在该开始工作了。 首先,构建并运行。

PotionsMaster是一个简单的应用程序,可让您添加,更新和删除列表中的成分。但是当您使用该应用程序时,会发现一个小问题。无论您做什么,该应用程序都不会保留您的数据!实际上,当您尝试创建,更新或删除成分时,它不会执行任何操作。但请放心,您将使用Realm使其发挥作用。

1. Project Structure

在深入研究并修复应用程序以开始酝酿下一个药水之前,请仔细阅读入门项目。它包含以下密钥文件:

  • Ingredient.swift:此结构表示一种成分。
  • IngredientStore.swift:这是商店模式(Store Pattern)的类实现。此类负责存储配料。您还可以在其中对这些成分进行操作。
  • IngredientsListView.swift:这是应用程序的主视图。此类显示一个列表List。有一个区域Section供您购买原料,另一个区域供您购买原料。
  • IngredientFormView.swift:您将使用此Form来创建和更新成分。

该项目使用Store Pattern来处理状态并将更改传播到UI。 IngredientStore提供了一份食材清单,包括您需要购买的食材和已经购买的食材。当用户与应用交互时,动作会改变状态state。然后,SwiftUI收到一个信号,以用新状态更新UI。


Working with Realm

Realm旨在解决当今应用程序的常见问题。它提供了许多优雅,易于使用的解决方案。它可用于多个平台,包括但不限于:

  • Swift/Objective-C
  • Java/Kotlin
  • JavaScript
  • .NET

关于Realm的最酷的部分是这些技能是可以转让的。一旦以一种语言学习了Realm基础知识,就很容易用另一种语言来学习它们。而且由于Realm是跨平台的数据库,因此它的API从一种语言到另一种语言的更改不会太大。

不过,Realm并不是万能的数据库。与SQLite不同,RealmNoSQL对象数据库。像任何其他NoSQL数据库一样,它也有优点和缺点。但是Realm是使多平台团队保持同步的绝佳选择。

1. Understanding the Realm Database

在开始设置Realm之前,您需要了解一些工作原理。

Realm使用文件来保存和管理数据库。应用中的每个Realm数据库都称为一个realm。您的应用程序可能有多个realm,每个realm都处理不同的对象域(domain of objects)。这有助于使您的数据库井井有条,简洁明了。而且,由于跨平台可用,因此您可以在iOSAndroid等平台之间共享预加载的Realm文件。真的很有帮助,对吧?

要打开Realm文件,只需实例化一个新的Realm对象。如果您未传递自定义文件路径,则Realm会在iOS上的Documents文件夹中创建default.realm文件。

有时,您可能想使用一个realm而不实际在磁盘上写入数据。该数据库为以下情况提供了便捷的解决方案: in-memory realms。这些在编写单元测试时很有用。您可以使用in-memory realms来为每个测试用例预加载,修改和删除数据,而无需实际在磁盘上进行写操作。

Realm Mobile Database不是Realm提供的唯一产品。该公司还提供Realm Sync,这是一种用于在多个设备之间以及在云中同步Realm数据库的解决方案。此外,Realm提供了一个很棒的应用程序来打开,编辑和管理您的数据库:Realm Studio


Setting up Realm

要开始使用Realm,您必须将其作为依赖项包含在您的项目中。 您可以使用许多依赖项管理工具,例如Swift Package ManagerCocoaPods。 本教程使用Swift Package Manager,但请随意使用您更喜欢的工具。

注意:如果您不熟悉Swift Package Manager,或者想了解更多信息,请查看我们的Swift Package Manager for iOS tutorial教程。

要设置您的依赖关系,请选择File ▸ Swift Packages ▸ Add Package Dependency…。 复制以下内容并粘贴到组合的搜索/输入框中:

https://github.com/realm/realm-cocoa.git

这是包含Realm软件包的GitHub存储库的位置。 点击Next

接下来,Xcode要求您为此程序包定义一些选项。 只需保留Up To Next Major的默认值即可使用Realm的最新版本。 (因此,在撰写本文时,此值从5.4.0(含)到6.0.0(不含)。)单击Next

最后,选择应添加到项目中的包装产品和target。 选择RealmRealmSwift,然后单击FinishXcode从存储库下载代码,并将Realm包添加到PotionsMasrer target

构建并运行以确保一切正常。

设置好Realm之后,就可以创建第一个data model了。


Defining Your Realm Data Model

要构建成分模型,请在Models组中添加一个新的Swift文件,并将其命名为IngredientDB.swift。 添加以下代码:

import RealmSwift
// 1
class IngredientDB: Object {
  // 2
  @objc dynamic var id = 0
  @objc dynamic var title = ""
  @objc dynamic var notes = ""
  @objc dynamic var quantity = 1
  @objc dynamic var bought = false

  // 3
  override static func primaryKey() -> String? {
    "id"
  }
}

这是您刚添加的代码的明细:

  • 1) 首先,定义您的类,继承自ObjectRealm是所有数据模型的基类。您将使用该类将成分保存在Realm中。
  • 2) 在这里,您定义要Realm存储的每个Ingredient属性。
  • 3) 最后,您重写primaryKey()告诉Realm哪个属性是模型的主键。Realm使用主键来强制唯一性。主键还提供了一种获取和更新数据的有效方法。

就这些!

您将Realm数据模型定义为常规Swift类。 Realm使用这些类在磁盘上写入数据,但是对Object子类有一些限制。由于跨平台性质,Realm仅支持有限的一组与平台无关的属性类型。其中一些属性是:

  • Bool
  • Int
  • Double
  • Float
  • String
  • Date
  • Data

Optional属性具有特殊限制。您可以将String,DateData声明为可选。其余部分,使用包装器类RealmOptional;否则,它们必须具有值。

每个属性都有@objc dynamic关键字。这使得它们可以在运行时通过Dynamic Dispatch访问。 Realm使用此SwiftObjective-C功能在读取和写入数据之间创建外观。访问属性时,Swift委托Realm负责为您提供所需的数据。

1. Defining Relationships

Realm还支持关系。您可以声明嵌套对象以创建多对一关系。您可以使用List创建多对多关系。声明列表时,Realm会将那些嵌套对象与数据模型一起保存。

2. Adding an Initializer

在继续之前,打开Ingredient.swift并在文件底部添加以下扩展名:

// MARK: Convenience init
extension Ingredient {
  init(ingredientDB: IngredientDB) {
    id = ingredientDB.id
    title = ingredientDB.title
    notes = ingredientDB.notes
    bought = ingredientDB.bought
    quantity = ingredientDB.quantity
  }
}

此扩展创建了一个方便的初始化程序,用于将IngredientDB映射到Ingredient。 您稍后将在项目中使用它。

现在,您已经定义了数据模型,是时候将对象添加到数据库中了。


Adding Objects to the Database

ViewModels组中,打开IngredientStore.swift。 通过在文件顶部添加以下行来导入RealmSwift

import RealmSwift

接下来,用以下代码替换create(title:notes:quantity :)的正文:

objectWillChange.send()

首先,您向SwiftUI发送信号。 由于IngredientStore是一个ObservableObject,因此SwiftUI订阅objectWillChange并通过重新加载其视图来响应信号。

接下来,将以下代码添加到方法中:

do {
  let realm = try Realm()

  let ingredientDB = IngredientDB()
  ingredientDB.id = UUID().hashValue
  ingredientDB.title = title
  ingredientDB.notes = notes
  ingredientDB.quantity = quantity
} catch let error {
  // Handle error
  print(error.localizedDescription)
}

首先,通过打开默认Realm创建一个Realm实例。 使用它,您可以编写,读取,更新和删除对象。 接下来,创建一个IngredientDB对象,并根据方法的参数设置其属性值。

您可以像其他任何Swift对象一样实例化和使用Realm数据模型。 您称这些unmanaged objects。 这意味着数据库尚不了解它们,并且任何更改都不会持久。 将对象添加到realm后,该对象将由Realm管理。 这意味着Realm将对象存储在磁盘上并跟踪其更改。

通过将以下几行代码添加到do块的末尾,将模型添加到realm

try realm.write {
  realm.add(ingredientDB)
}

您可以通过在Realm上调用write来启动写事务。 您对realm进行的每个操作都必须在此写事务块(transaction block)内,包括添加,删除和更新。 在事务内部,您将新的IngredientDB实例添加到RealmRealm现在正在存储对象并跟踪其更改,使其成为managed object

构建并运行,然后继续创建成分! 点击New Ingredient,为其命名,然后点击保存。

但是,等等,仍然有些不对劲。 您创建了ingredient,但是什么也没有发生! 它仍然列出与以前相同的ingredient

那是因为IngredientsStore尚未从Realmfetch ingredients - 它仍在使用mock数据。 接下来,您将解决此问题。


Fetching Objects

再次打开IngredientStore.swift,然后找到以下内容:

var ingredients: [Ingredient] = IngredientMock.ingredientsMock
var boughtIngredients: [Ingredient] = IngredientMock.boughtIngredientsMock

使用下面替换上面代码:

private var ingredientResults: Results<IngredientDB>
private var boughtIngredientResults: Results<IngredientDB>

Realm获取对象时,数据库将返回Results类型。 此类型表示从查询检索到的对象的集合。

现在,在刚添加的两行下面添加以下初始化程序:

// 1
init(realm: Realm) {
  // 2
  ingredientResults = realm.objects(IngredientDB.self)
    .filter("bought = false")
  // 3
  boughtIngredientResults = realm.objects(IngredientDB.self)
    .filter("bought = true")
}

以下是上述初始化程序中的操作:

  • 1) 首先,您将收到一个Realm实例。 您将使用此实例来获取ingredients
  • 2) 接下来,您从realmfetch ingredient,并用boughtfalse过滤它们。
  • 3) 然后,您从realmfetch ingredient,并用boughttrue过滤它们。

最后,在初始化程序之后插入以下代码:

var ingredients: [Ingredient] {
  ingredientResults.map(Ingredient.init)
}

var boughtIngredients: [Ingredient] {
  boughtIngredientResults.map(Ingredient.init)
}

这些属性将RealmResult转换为常规数组。 示例项目的UI使用这些计算出的属性将数据库模型映射到视图。

由于IngredientStore现在在其初始值设定项中需要一个Realm,因此需要提供它。 打开ScenceDelegate.swift。 在import SwiftUI语句之后,通过插入以下内容导入RealmSwift

import RealmSwift

接下来,将scene(_:willConnectTo:options:)中的代码更改为此:

if let windowScene = scene as? UIWindowScene {
  do {
    // 1
    let realm = try Realm()
    let window = UIWindow(windowScene: windowScene)
    // 2
    let contentView = ContentView()
      .environmentObject(IngredientStore(realm: realm))
    window.rootViewController = UIHostingController(rootView: contentView)
    self.window = window
    window.makeKeyAndVisible()
  } catch let error {
    // Handle error
    fatalError("Failed to open Realm. Error: \(error.localizedDescription)")
  }
}

这是您正在做的事情:

  • 1) 您创建一个新的Realm实例。
  • 2) 您可以使用Realm实例实例化IngredientStore,并将其添加到ContentViews环境中。

构建并运行。 现在创建一个ingredient,看看魔术!

RealmLive Objects一起使用。 当您向Realm中添加ingredient时,IngredientResults会自动更新,而无需每次都获取它。 SwiftUI收到信号以使用新的最新视图更新UI。 感觉像魔术,对不对? 继续创造更多ingredients

现在您可以成功添加ingredients了,该构建用于更新现有ingredients的功能的时候了。


Updating Objects

PotionsMaster的一项关键功能是能够将成分切换到BOUGHT列表。 现在,如果您点击购买按钮,则什么也不会发生。 要解决此问题,您可以使用Realm更新磁盘上的成分。

1. Toggling Ingredients to BOUGHT

要将ingredient移至BOUGHT列表,您需要在磁盘上将bought的属性值更新为true

打开IngredientStore.swift并将toggleBought(ingredient :)的内容替换为以下内容:

// 1
objectWillChange.send()
do {
  // 2
  let realm = try Realm()
  try realm.write {
    // 3
    realm.create(
      IngredientDB.self,
      value: ["id": ingredient.id, "bought": !ingredient.bought],
      update: .modified)
  }
} catch let error {
  // Handle error
  print(error.localizedDescription)
}

这是这段代码中发生的事情:

  • 1) 您向SwiftUI发送一个信号,指示该对象即将更改。
  • 2) 您打开默认realm
  • 3) 您开始一个新的写事务并调用create(_:value:update :),并传递更新的值和.modifiedcase。 这告诉Realm使用字典中的值更新数据库。 建立字典时,您必须包含对象的id。 如果具有该id的对象已经存在,Realm将使用新值更新它。 否则,Realm将在磁盘上创建一个新对象。

构建并运行。 现在,通过点击其单元格右侧的圆形图标来购买一种ingredient

在更新bought时,Realm会将此更改同时通知IngredientResultsbuyIngredientResults。 更新的ingredient将移至buyedIngredientResultsSwiftUI在列表List上添加动画效果! 多么酷啊?

更新对象的另一种方法是将其属性设置为新值。 同样,您可以在写事务中执行此操作。 然后,Realm将更新磁盘上的每个值。

2. Updating Other Properties

现在您知道如何更新对象,可以使用Realm轻松更新其他属性。 将update(ingredientID:title:notes:quantity :)的正文更改为以下代码:

// 1
objectWillChange.send()
do {
  // 2
  let realm = try Realm()
  try realm.write {
    // 3
    realm.create(
      IngredientDB.self,
      value: [
        "id": ingredientID,
        "title": title,
        "notes": notes,
        "quantity": quantity
      ],
      update: .modified)
  }
} catch let error {
  // Handle error
  print(error.localizedDescription)
}

这是这段代码中发生的事情:

  • 1) 再次,您使用objectWillChange发送一个信号,告诉SwiftUI重新加载UI。
  • 2) 您打开默认realm
  • 3) 您在写事务中调用create(_:value:update :)。 此调用将更新ingredient的值。

这类似于您上面添加的用于购买ingredient的代码。 您调用create(_:value:update :),并传递更新后的值。 Realm会更新磁盘上的值,并将更改的结果通知ingredientResults。 然后,SwiftUI使用这些更改更新UI

构建并运行。 点按一种ingredient的名称以再次打开该表格。 编辑一些字段,然后点击Update

现在,剩下的就是删除ingredients了!


Deleting Objects

轻按buy按钮可将配料移至BOUGHT部分。 但是一旦它存在就无法摆脱它。 轻按垃圾桶图标不会执行任何操作。

要解决此问题,请打开IngredientStore.swift。 用以下代码替换delete(ingredientID :)的正文:

// 1
objectWillChange.send()
// 2
guard let ingredientDB = boughtIngredientResults.first(
  where: { $0.id == ingredientID }) 
  else { return }

do {
  // 3
  let realm = try Realm()
  try realm.write {
    // 4
    realm.delete(ingredientDB)
  }
} catch let error {
  // Handle error
  print(error.localizedDescription)
}

以下是删除此代码中ingredient的方法:

  • 1) 再次,您使用objectWillChange发送信号,请求SwiftUI重新加载UI。
  • 2) 您找到要从buyedIngredientResults中删除的ingredient
  • 3) 您打开默认realm
  • 4) 最后,调用delete,传递要删除的对象。

构建并运行。 购买一种ingredient,然后点击删除按钮将其从列表中删除。


Adding a New Property to a Realm Object

在开发过程中,数据模型会不断增长和发展。 属性类型可能会更改,并且您可能需要添加或删除属性。 使用Realm,更改数据模型就像更改任何其他Swift类一样容易。

在本部分中,您将添加一个新属性,以按颜色识别ingredients

打开IngredientDB.swift并在bought项下添加一个新属性:

@objc dynamic var colorName = "rw-green"

接下来,在Ingredient.swift中,添加以下属性:

var colorName = "rw-green"

您还需要更新初始化程序以设置colorName。 在文件的初始化程序的底部添加以下行:

colorName = ingredientDB.colorName

在上面的三行代码中,您添加了一个属性,用于将颜色名称存储在Realm上并在视图中进行映射。

就这些! 这些模型已准备好存储颜色名称。 接下来,您将更新IngredientStore.swift将此新属性保存在数据库中。

1. Storing the New Property in Realm

打开IngredientStore.swift,找到以下代码:

func create(title: String, notes: String, quantity: Int) {

使用下面代码替换:

func create(title: String, notes: String, quantity: Int, colorName: String) {

现在,在设置其他属性(如quantitynotes)之后插入以下行:

ingredientDB.colorName = colorName

这将添加一个新参数colorName,并将其分配给IngredientDB

仍然在IngredientStore.swift中,找到以下行:

func update(ingredientID: Int, title: String, notes: String, quantity: Int) {

将它更换为:

func update(
  ingredientID: Int,
  title: String,
  notes: String,
  quantity: Int,
  colorName: String
) {

最后,在update中,找到这个代码:

realm.create(
  IngredientDB.self,
  value: [
    "id": ingredientID,
    "title": title,
    "notes": notes,
    "quantity": quantity
  ], 
  update: .modified)

使用下面代码进行替换:

realm.create(
  IngredientDB.self,
  value: [
    "id": ingredientID,
    "title": title,
    "notes": notes,
    "quantity": quantity,
    "colorName": colorName
  ],
  update: .modified)

此代码将参数colorName添加到更新中。 调用create(_:value:update :)时,它将其添加到value字典中。

现在,createupdate方法都需要一个colorName

但是Xcode认识到IngredientFormFormView在调用这些方法时没有传递colorName,并且会产生一些错误。

要解决此问题,请打开IngredientForm.swift。 在属性quantity之后添加以下代码:

@Published var color = ColorOptions.rayGreen

现在找到init(_:ingredient :)并在底部添加以下行:

color = ColorOptions(rawValue: ingredient.colorName) ?? .rayGreen

在此处,您添加了一个属性,用于在用户创建或更新ingredient时存储颜色。

接下来,打开IngredientFormView.swift并在saveIngredient()中找到以下代码:

store.create(
  title: form.title,
  notes: form.notes,
  quantity: form.quantity)

替换它使用下面:

store.create(
  title: form.title,
  notes: form.notes,
  quantity: form.quantity,
  colorName: form.color.name)

updateIngredient()中,您需要在调用中传递colorName进行update。 为此,请找到以下代码:

store.update(
  ingredientID: ingredientID,
  title: form.title,
  notes: form.notes,
  quantity: form.quantity)

替换上面,使用下面:

store.update(
  ingredientID: ingredientID,
  title: form.title,
  notes: form.notes,
  quantity: form.quantity,
  colorName: form.color.name)

现在,您已经解决了IngredientFormFormView不将colorName传递给IngredientStore的问题。

构建并运行。 你得到...

该应用程序崩溃了! Realm引发迁移错误:Migration is required due to the following errors:,但是为什么会这样呢?


Working With Migrations

启动应用程序时,Realm会在您的代码中扫描带有Object子类的类。找到一个模型后,它将创建一个用于将模型映射到数据库的schema

当您更改数据模型时,new schema与数据库中的schema将不匹配。如果发生这种情况,Realm会引发错误。您必须告诉Realm如何将旧schema迁移到新schema。否则,它不知道如何将旧对象映射到新schema

由于您向IngredientDB添加了新属性colorName,因此必须为其创建迁移。

注意:您可以在开发期间通过实例化Realm.Configuration时将true传递给deleteRealmIfMigrationNeeded来解决。这告诉Realm,如果需要迁移,它应该删除其文件并创建一个新文件。

1. Creating a Migration

Models组中,创建一个名为RealmMigrator.swift的文件。

现在,将此代码添加到新文件中:

import RealmSwift

enum RealmMigrator {
  // 1
  static private func migrationBlock(
    migration: Migration,
    oldSchemaVersion: UInt64
  ) {
    // 2
    if oldSchemaVersion < 1 {
      // 3
      migration
        .enumerateObjects(ofType: IngredientDB.className()) { _, newObject in
          newObject?["colorName"] = "rw-green"
        }
    }
  }
}

细目如下:

  • 1) 您定义迁移方法。 该方法接收迁移对象和oldSchemaVersion
  • 2) 您检查文件固定schema的版本,以确定要运行的迁移。 每个schema都有一个版本号,从零开始。 在这种情况下,如果旧schema是第一个模式(在添加新属性之前),请运行迁移。
  • 3) 最后,为Realm中的每个旧的和新的IngredientDB对象,为新属性分配一个默认值。

Realm使用migrationBlock来运行迁移并更新任何必要的属性。

RealmMigrator的底部,添加以下新的static方法:

static func setDefaultConfiguration() {
  // 1
  let config = Realm.Configuration(
    schemaVersion: 1,
    migrationBlock: migrationBlock)
  // 2
  Realm.Configuration.defaultConfiguration = config
}

这是您在这段代码中所做的:

  • 1) 您可以使用migrationBlock创建一个Realm.Configuration的新实例,并将该schema的当前版本设置为1
  • 2) 您设置了Realm的新默认配置。

最后,在SceneDelegate.swift中,在scene(_:willConnectTo:options:)顶部调用此新方法:

RealmMigrator.setDefaultConfiguration()

Realm使用此配置来打开默认数据库。 发生这种情况时,Realm会检测到文件持久化schema与新schema之间的不匹配。 然后,它通过运行刚创建的迁移功能来迁移更改。

现在构建并再次运行。 这次崩溃不见了!

您已成功将新属性添加到IngredientDB。 您已经做好了迁移的准备。 现在是时候更新表格,以便用户选择颜色了!


Adding a New Field to the Form

打开IngredientFormView.swift并找到注释// TODO: Insert Picker here。 将此代码插入注释行下方:

Picker(selection: $form.color, label: Text("Color")) {
  ForEach(colorOptions, id: \.self) { option in
    Text(option.title)
  }
}

这会将新的picker view添加到IngredientFormView。 该picker使用户可以选择颜色。

接下来,打开IngredientRow.swift并找到注释// TODO: Insert Circle view here。 在注释后添加以下代码:

Circle()
  .fill(Color(ingredient.colorName))
  .frame(width: 12, height: 12)

在这里,您要向每个ingredient行添加一个圆形视图。 您用该ingredient的颜色填充圆圈。

构建并运行以查看更改。 现在创建一个新ingredient并为其选择颜色。

很好! 现在,您可以继续列出要酿造的特殊药水所需的所有成分。

在本SwiftUI Realm教程中,您学习了如何使用SwiftUI从Realm创建,更新,获取和删除对象。 除了基础知识之外,您还了解了迁移以及如何创建迁移。

要了解有关Realm的更多信息,可以参考其official documentation

如果您想了解更多关于SwiftUI的信息,请参阅我们的SwiftUI by Tutorials.。

后记

本篇主要讲述了基于RealmSwiftUI的数据持久化简单示例,感兴趣的给个赞或者关注~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342