译自 www.hackingwithswift.com/books/ios-s…
更多内容,欢迎关注公众号 「Swift花园」
喜欢文章?不如来个 🔺💛➕三连?关注专栏,关注我 🚀🚀🚀
表单验证和禁用
SwiftUI 的Form视图以一种非常便捷的方式存储用户输入的内容,但是有时候更进一步是重要的 —— 检查输入内容以确保它们有效,然后再继续操作。
好吧,我们有一个专门用于这个目的的 modifier:disabled()。这需要检查一个条件,如果条件为true,则它附着的任何内容都将不响应用户的输入 —— 按钮不能点击,滑块不能拖动,等等。你可以在这里使用简单的属性,但是任何条件都可以:读取一个计算属性,调用方法,等等。
为了演示这一点,下面是一个接收用户名和电子邮件地址的表单:
struct ContentView: View {
@State private var username = ""
@State private var email = ""
var body: some View {
Form {
Section {
TextField("Username", text: $username)
TextField("Email", text: $email)
}
Section {
Button("Create account") {
print("Creating account…")
}
}
}
}
}在这个实例中,除非两个字段都填写,否则我们不希望用户创建帐户。因此,我们可以通过像下面这样添加disabled()modifier,来禁用包含“创建账户”按钮的表单段。
Section {
Button("Create account") {
print("Creating account…")
}
}
.disabled(username.isEmpty || email.isEmpty)它的意思是“如果username或者email是空的,则禁用这个段”,这正是我们想要的。
你可能会发现,把你的条件提取为单独的计算属性是值得的,比如这样:
var disableForm: Bool {
username.count < 5 || email.count < 5
}现在,你可以你的 modifier 中引用它:
.disabled(disableForm)不管你是如何做的,我都希望你尝试运行应用,看看 SwiftUI 如何处理禁用的按钮 —— 当我们没有通过测试时,按钮的文本变为灰色,只要测试通过,按钮就会亮起蓝色。
译自 www.hackingwithswift.com/books/ios-s…
获取基本的订单详情
这个项目的第一步是创建一个用户下单界面,接收订单的基本信息:他们想要多少个纸杯蛋糕,哪些款式,是否需要任何特殊定制。
在进入UI之前,我们需要先定义数据模型。之前,我们曾经在简单值类型上使用@State,在引用类型上使用@ObservedObject,并且研究了如何让一个ObservableObject类包含结构体,以便我们同时获得两者的好处。
在这里,我们将采用不同的解决方案:我们要用一个单一的类来存储所有的数据,这些数据会在不同屏之间传递。也就是说,我们应用里的所有屏共享相同的数据,你会看到,这种方式非常好用。
就现在来说,这个类还不需要很多属性:
- 蛋糕的类型,加上所有可能选项的静态数组。
- 用户想要订的蛋糕数量。
- 用户是否有特殊要求,它会在我们的 UI 中显示或者隐藏额外的选项。
- 用户是否要在蛋糕上加些糖霜。
- 用户是否要在蛋糕上加些巧克力米。
上面的每一个属性都要在自己改变时更新 UI,这意味着我们需要@Published来标记它们,并且让整个类遵循ObservableObject。
因此,请创建一个新的 Swift 文件,取名 Order.swift,去掉 Foundation 的导入,然后添加下面的代码:
class Order: ObservableObject {
static let types = ["香草", "草莓", "巧克力", "彩虹"]
@Published var type = 0
@Published var quantity = 3
@Published var specialRequestEnabled = false
@Published var extraFrosting = false
@Published var addSprinkles = false
}现在,我们可以通过添加下面的属性在ContentView中创建单一实例:
@ObservedObject var order = Order()这是创建订单的唯一地方 —— 我们应用里的其他屏都会被传入这个属性,以便大家使用相同的数据。
我们将分三部分构建这一屏的 UI,首先是纸杯蛋糕的类型和数量。第一部分将显示一个选择器,用户可以从香草,草莓,巧克力和彩虹蛋糕中进行选择,然后是一个分档器,可以在 3 到 20 之间选择数量。所有这些内容会被包进一个表单,而表单本身处于导航视图中,以便我们可以设置标题。
把下面的代码放进ContentView的body属性:
NavigationView {
Form {
Section {
Picker("Select your cake type", selection: $order.type) {
ForEach(0..<Order.types.count) {
Text(Order.types[$0])
}
}
Stepper(value: $order.quantity, in: 3...20) {
Text("Number of cakes: \(order.quantity)")
}
}
}
.navigationBarTitle("Cupcake Corner")
}在我们添加第二部分和第三部分之前,我想要你编译并运行代码,然后尝试一下。我们的两个表单应该可以正常工作,但你可能 —— 只是可能 —— 在 Xcode 中看到一条常规的烦人的消息,有关ForEach:“count (4) != its initial count (1)”。
我之所以说“可能”,是因为这感觉就像是另一个 SwiftUI 的 bug:即使我们的ForEach正在使用固定的数据(即Order.types数组中的项目数),SwiftUI 似乎也很难处理它。如果你没有看到这个错误,就不必担心,但假如你的确看到了它,我想向你展示如何解决它:我们需要给它一个显式的标识符。
把你的ForEach修改成这样:
ForEach(0..<Order.types.count, id: \.self) {这样就完全清除掉问题了,不过老实说,我寄希望于这个错误会在以后的 SwiftUI 更新中消失。
我们表单的第二部分将包含三个分别绑定到specialRequestEnabled,extraFrosting和addSprinkles的开关。但是,只有第一个开关启用的前提下,第二个和第三个开关才应该可见,因此我们要把它们包装到一个条件中。
现在,添加第二部分:
Section {
Toggle(isOn: $order.specialRequestEnabled.animation()) {
Text("Any special requests?")
}
if order.specialRequestEnabled {
Toggle(isOn: $order.extraFrosting) {
Text("Add extra frosting")
}
Toggle(isOn: $order.addSprinkles) {
Text("Add extra sprinkles")
}
}
}继续再次运行应用,然后尝试一下 —— 注意刚才我绑定第一个开关时附着了一个animation()modifier,以便第二个和第三个开关可以平滑地滑入和滑出。
但是,还有另一个 bug,这一次它是我们自己制造的:如果我们启用特殊要求,然后启用“额外的糖霜”和“额外的巧克力米”中的一个或者两个,然后再禁用特殊要求,则我们之前的特殊要求选项会保持激活。也就是说,如果我们重新启用特殊要求,那么之前的特殊要求选项还处于激活状态。
这种问题不难解决 —— 如果你对代码里的每一层都做了编程:你的服务端,你的数据库等都在specialRequestEnabled被设置为false的时候忽略extraFrosting和addSprinkles。但是,一个更好的(也更安全的) 的方案是确保specialRequestEnabled被设置为false时,extraFrosting和addSprinkles也都被设置为false。
我们可以通过添加一个didSet属性观察者给specialRequestEnabled来实现这个方案。添加下面的代码:
@Published var specialRequestEnabled = false {
didSet {
if specialRequestEnabled == false {
extraFrosting = false
addSprinkles = false
}
}
}我们的第三部分最简单,因为它只是一个指向下一屏的NavigationLink。尽管我们还没有第二屏,可以快速添加一个:创建一个新的 SwiftUI 视图,取名AddressView,然后给它一个orderObservedObject属性,像这样:
struct AddressView: View {
@ObservedObject var order: Order
var body: some View {
Text("Hello World")
}
}
struct AddressView_Previews: PreviewProvider {
static var previews: some View {
AddressView(order: Order())
}
}我们很快会让这个新视图更加有用,不过目前我们可以先回到 ContentView.swift ,为表单添加最后一部分。这将创建一个指向AddressView的NavigationLink,传入当前的订单对象。
添加最后一部分:
Section {
NavigationLink(destination: AddressView(order: order)) {
Text("Delivery details")
}
}这样就完成了我们的第一屏。在我们继续之前最后尝试一下 —— 你应该能够选择蛋糕类型,选择数量,以及切换所有的开关。
我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~