[本文翻译自hackingwithswift,点击链接阅读原文]
如果您在SwiftUI预览窗口中查看,您将看到标准的iOS选择器界面——一个弹出的选项菜单。默认情况下,它将显示第一个选项,因为它读取了paymentType
的值,我们将其设置为“现金”。然而,当用户显示菜单时,他们的选择会发生变化——他们可能会选择“信用卡”或“iDine积分”而不是现金。
因此,这个选择器不仅读取paymentType
的值,还写入值。这就是所谓的双向绑定,因为对paymentType
值的任何更改都将更新拣选器,对拣选器的任何更改都将更新paymentType
。
This is where the dollar sign comes in: Swift property wrappers use that to provide two-way bindings to their data, so when we say $paymentType
SwiftUI will write the value using the property wrapper, which will in turn stash it away and cause the UI to refresh automatically.
乍一看,所有这些@和$s可能看起来有点不迅速,如果你来自UIKit,你可能不习惯以这种方式工作。然而,它们允许我们获得需要很多麻烦的功能:
没有
@State
我们将无法更改结构中的属性,因为结构是固定值。没有
StateObject
我们将无法创建在应用程序期间保持活力的类。如果没有
@EnvironmentObject
我们将无法从应用程序中的其他地方接收共享数据。如果没有
ObservableObject
当外部值发生变化时,我们就不会收到通知。如果没有
$property
双向绑定,我们需要手动更新值。
无论如何,这是我们的基本选择器完成,因此如果我们返回OrderView.swift,我们可以更新我们的代码,以便它显示我们新的CheckoutView
结构,而不是一些显示“Checkout”的文本。
找到这个代码:
NavigationLink("Place Order") {
Text("Check out")
}
并用这个替换它:
NavigationLink("Place Order") {
CheckoutView()
}
现在尝试运行应用程序,然后转到“订单”标签,然后按“下订单”。结果是......嗯,不太完美,让我们这样说吧。尽管为了走到这一步付出了很多努力。
好吧,我们将在CheckoutView.swift中只更改一个单词,这应该会让所有工作都感到合理。
在CheckoutView
,我想让你将VStack
更改为Form
,然后按Cmd+R再次尝试该应用程序。你能发现区别吗?
以前,我们有一个没有标题的普通弹出菜单,但现在我们处于一个表单中,我们得到了一个表格行,显示我们的选择器的标题及其当前选定的值。
这就是SwiftUI对用户界面的声明方法的力量:我们说出我们想要的行为,而不是它的精确风格,SwiftUI将根据其使用的上下文自动调整它。
让我们通过添加两个组件继续我们的表格:一个允许用户选择他们是否有iDine会员卡,另一个允许他们输入卡号。两者都需要双向绑定,就像我们的选择器一样,所以让我们从两个新的@State
属性开始:
@State private var addLoyaltyDetails = false
@State private var loyaltyNumber = ""
现在,我们可以将控件添加到我们的表单中来表示这些——Toggle
等同于UISwitch
,TextField
等同于UITextField
。在我们现有的表格部分中添加这两个:
Toggle("Add iDine loyalty card", isOn: $addLoyaltyDetails)
TextField("Enter your iDine ID", text: $loyaltyNumber)
那里没有很多代码,但值得一提的是一些细节:
两个控件都绑定到我们刚刚制作的那些
@State
属性。Toggle
开关里面有一些文本,这些文本将自动显示在左侧作为描述。TextField
有一些占位符文本,因此用户知道要在那里输入什么。
在你运行应用程序之前,我想先谈谈另一个变化。我们刚刚添加的那个文本字段——它应该一直在那里,还是只有在启用切换开关时才存在?
我们绑定了Toggle
到addLoyaltyDetails
的值,这意味着当用户打开或关闭它时,Boolean将设置为true或false。如果文本字段只有在打开切换时才可见,那不是很好吗?
嗯,事实证明这很容易做到。尝试在以下条件下包装文本字段:
Toggle("Add iDine loyalty card", isOn: $addLoyaltyDetails)
if addLoyaltyDetails {
TextField("Enter your iDine ID", text: $loyaltyNumber)
}
当您现在运行程序时,您会看到更改切换状态会显示或隐藏文本字段。如果你想清楚,这一切都应该有意义:
切换对
addLoyaltyDetails
属性有双向绑定。这意味着当切换被更改时,属性会更新。
该属性标有
@State
。当任何
@State
或@EnvironmentObject
更改其值时,SwiftUI将重新调用body
属性。该
body
属性直接读取addLoyaltyDetails
的值,以决定是否创建文本字段。
为了改善效果,请修改Toggle
上的绑定,使其动画化它引起的任何变化:
Toggle("Add iDine loyalty card", isOn: $addLoyaltyDetails.animation())
这将导致会员卡行顺利地滑入和滑出。
让我们尝试另一个常见的控制:分割控制。在SwiftUI中,这实际上只是一个带有修饰符的Picker
,因此它的工作方式完全相同——我们给它一个双向绑定来存储其选择,然后使用ForEach
在数组上循环,以创建一些选项可供选择。
对于这个屏幕,我们可以使用分段控件来表示用户可以选择的各种提示百分比。因此,首先添加此属性来存储我们想要显示的选项:
let tipAmounts = [10, 15, 20, 25, 0]
现在添加此属性来存储所选的小费金额:
@State private var tipAmount = 15
我们现在可以将所有这些放入我们的表格中的分割控制中。我将把这个放在我们表格的新部分,因为它允许我们添加一个标题,使用户界面更清晰:
Section("Add a tip?") {
Picker("Percentage:", selection: $tipAmount) {
ForEach(tipAmounts, id: \.self) {
Text("\($0)%")
}
}
.pickerStyle(.segmented)
}
我们将在我们的表格中再添加一个组件,这是一个实际确认订单的按钮。我们一会儿就会回到它的确切功能,因为我们首先需要看看其他东西。
这是表格的最后一部分:
Section("Total: $100") {
Button("Confirm order") {
// place the order
}
}
是的,我知道总订单价值是错误的,但现在只需运行应用程序。
我们在ItemDetail
中添加了一个按钮,它是透明背景上的蓝色文本,以屏幕为中心。现在,我们的表单中有一个按钮,它与众不同:它是蓝色文本,左对齐,如果你点击它,整个行会闪烁灰色。这是SwiftUI的表单系统改变其内部组件设计和行为的另一个例子。
评论区