通过使用自定义绑定来提供状态变量的替代方案。
定义与应用中其他视图绑定的可靠来源的最常见方法是使用 State 属性包装器声明状态变量。然而,在极少数情况下,可靠来源是动态的,无法使用 @State 属性进行定义。例如,此示例应用需要使用菜谱的 ID 检索菜谱作为可靠来源。该应用通过创建一个返回自定义绑定的计算属性来实现此目的。
要试验代码,请下载项目文件并在 Xcode 中打开示例。
第 1 部分
指定事实来源 此示例应用在自定义视图 Detail View 中显示食谱的详细信息。该视图仅识别食谱 ID,但不知道食谱本身,因此它使用该 ID 从食谱盒(包含所有食谱的数据存储)中检索食谱。由于视图需要检索食谱,因此它使用自定义绑定作为食谱的真实来源,而不是为食谱声明状态变量。
笔记 使用自定义绑定是 SwiftUI 的一个实用功能,但并非总是最佳选择。请将其限制在无法使用状态变量或对象的情况下使用。在大多数情况下,将真实来源定义为 State 变量(用于视图的本地状态)或 State Object (用于共享数据模型),以便 SwiftUI 为您管理值或对象。
步骤 1
为了获取 Detail View 中的配方值,此示例实现了计算属性 recipe 而不是声明状态变量。
计算的 recipe 属性不会返回 Recipe 。相反,它会返回 Recipe 类型的自定义 Binding 。这允许视图与其他视图共享该配方作为真实来源。
import SwiftUI
struct DetailView: View {
@Binding var recipeId: Recipe.ID?
@EnvironmentObject private var recipeBox: RecipeBox
@State private var showDeleteConfirmation = false
private var recipe: Binding<Recipe> {
Binding {
if let id = recipeId {
return recipeBox.recipe(with: id) ?? Recipe.emptyRecipe()
} else {
return Recipe.emptyRecipe()
}
} set: { updatedRecipe in
recipeBox.update(updatedRecipe)
}
}
var body: some View {
ZStack {
if recipeBox.contains(recipeId) {
RecipeDetailView(recipe: recipe)
.navigationTitle(recipe.wrappedValue.title)
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
#endif
.toolbar {
RecipeDetailToolbar(
recipe: recipe,
showDeleteConfirmation: $showDeleteConfirmation,
deleteRecipe: deleteRecipe)
}
} else {
RecipeNotSelectedView()
}
}
}
private func deleteRecipe() {
recipeBox.delete(recipe.id)
}
第 2 步
Binding 提供对值的读写访问。为了提供对配方值的这种访问,计算出的 recipe 属性使用 init(get: set:) 初始化方法创建绑定。
步骤3
绑定的 get 闭包使用 recipe Id 从数据存储 recipe Box 中检索配方。
如果配方不再存在或无法找到,则闭包将返回一个空配方。
步骤4
在 set 闭包中,绑定使用新的配方值( updated Recipe 来更新配方框。
每当绑定的配方值发生数据变化时,就会发生此更新;例如,在某人更改配方的评级之后。
步骤5
Detail View 将 recipe 作为绑定值传递给 Recipe Detail View ,这允许详细视图读取和写入配方值。
重要 由于计算属性 recipe 返回一个 Binding ,因此无需像传递状态变量作为绑定时那样添加美元符号 ( $ ) 前缀。对于状态变量(使用 State 属性包装器定义的变量),美元符号 ( $ ) 前缀告诉 SwiftUI 传递 projected Value ,即 Binding 。
步骤6
navigation Title(_😃 修饰符接受字符串值而不是字符串值的绑定,因此视图传递 recipe 绑定的 wrapped Value 。
wrapped Value 是绑定所引用的底层值。由于计算后的 recipe 属性返回一个绑定,因此其包装值就是实际的配方值。因此, recipe .wrapped Value .title 获取 recipe 绑定的 wrapped Value ,然后将配方值的 title 属性传递给 navigation Title(_😃 。