译自 www.hackingwithswift.com/books/ios-s…
更多内容,欢迎关注公众号 「Swift花园」
喜欢文章?不如来个 🔺💛➕三连?关注专栏,关注我 🚀🚀🚀
校验地址
项目的第二步是让用户往表单里输入地址。作为这个步骤的一部分,我们将先对地址添加一些校验 —— 只有地址正确的情况下我们才继续前往第三步。
我们通过往之前实现的AddressView结构体里添加一个Form视图来实现校验。表单将包含四个文本框:名称,街道地址,城市和 zip 码。我们可以利用NavigationLink来跳转到下一屏,在下一屏用户可以看到订单最后的价格,并且结账下单。
为了便于理解,我们先从一个叫CheckoutView的新视图开始,在这个视图会在用户填好地址之后跳转。所以这里先用它占位,之后再回来完善。
创建一个叫CheckoutView的新 SwiftUI 视图,传入和AddressView相同的的Order属性:
struct CheckoutView: View {
@ObservedObject var order: Order
var body: some View {
Text("Hello, World!")
}
}
struct CheckoutView_Previews: PreviewProvider {
static var previews: some View {
CheckoutView(order: Order())
}
}接下来我们开始实行AddressView。像前面说过的,这会是一个表单,包含四个和Order对象里相应的四个属性绑定在一起的文本框,外加一个用户控制跳转到下单视图的NavigationLink。
首先,我们需要在Order里增加四个新的@Published属性,用于存储配送细节:
@Published var name = ""
@Published var streetAddress = ""
@Published var city = ""
@Published var zip = ""然后把AddressView的body替换成下面这样:
Form {
Section {
TextField("Name", text: $order.name)
TextField("Street Address", text: $order.streetAddress)
TextField("City", text: $order.city)
TextField("Zip", text: $order.zip)
}
Section {
NavigationLink(destination: CheckoutView(order: order)) {
Text("Check out")
}
}
}
.navigationBarTitle("Delivery details", displayMode: .inline)如你所见,上面的代码把order对象又传给了CheckoutView,这意味着我们现在有三个视图指向同一份数据。
然后运行应用,我希望你看到其中的要点。在第一屏输入数据,在第二屏也输入一些数据,然后跳转回初始屏,然后再快速跳转到第三屏。
你会发现不管你在哪一屏,之前输入的所有数据都保存完好。是的,这是对数据采用 class 实现带来的自有效应,这是一个无需我们做额外工作就能获得的即时特性 —— 假如我们用的是结构体,那么任何我们输入的地址信息都会在我们跳转回初始视图时消失 。如果你真的打算用结构体来承载数据,那么你需要参考 iExpense 项目的做法;在选项时,记住类和结构体的这一点区别。
现在AddressView已经工作,是否对用户推进到下单过程做出控制了。我们需要什么条件呢?
我们将检查名字,街道地址,城市,以及 zip 属性都不为空。我通常更喜欢在数据里实现下面这样一个计算属性来实现校验:
var hasValidAddress: Bool {
if name.isEmpty || streetAddress.isEmpty || city.isEmpty || zip.isEmpty {
return false
}
return true
}然后,我们这个校验同 SwiftUI 的disabled()modifier 联系起来 —— 它可以被附着于任意视图,但条件满足时,视图将停止对用户交互的响应。
我们的案例中,我们需要检查的条件是上面刚写好的计算属性,hasValidAddress。如果它为 false,那么表单里包含NavigationLink的段应该禁用,因为我们需要用户先填好配送细节才能继续。
把下面这个 modifier 添加到AddressView的第二个Section:
.disabled(order.hasValidAddress == false)代码如下:
Section {
NavigationLink(destination: CheckoutView(order: order)) {
Text("Check out")
}
}
.disabled(order.hasValidAddress == false)再次运行应用,你会发现,只有四个文本框都至少包含一个字符时,订单才能继续。当这个条件不满足时,按钮会置灰,以给到用户明显的反馈,表明它是不可交互的。
译自 Preparing for checkout
准备结账
我们应用的最后一屏是CheckoutView,它包含两部分:第一部分是基本的用户界面,对你来说几乎没有挑战;但第二部分是全新的:我们需要把Order类编码成 JSON,发送到网络上,然后获得一个响应。
我们马上会了解到编码和传输是怎么回事,但在这之前让我先解决掉简单的部分:为CheckoutView创建一个用户界面。具体来说,我们要用图像创建一个ScrollView,包含总价的文本视图,以及一个下单按钮,用于触发后续的网络流程。
对于图像,我提供了两张图片,可以从github.com/twostraws/H…下载到。请从 SwiftUI/project10-files 文件里找到它们,把cupcakes@2x.jpg 和cupcakes@3x.jpg 都拷贝到你的 asset catalog。我们需要用到GeometryReader来重新调整图像的尺寸以适应屏幕的宽度,避免它在更小的设备上被拉伸。
对于订单费用,规则如下:
- 每个纸杯蛋糕有 $2 每个的基础价格
- 复杂一点的蛋糕增加一点费用
- 额外的糖霜每个蛋糕收 $1
- 额外的巧克力粉每个蛋糕收 50 分
我们把所有这些逻辑封装成一个计算属性,放在Order里,像这样:
var cost: Double {
// $2 per cake
var cost = Double(quantity) * 2
// complicated cakes cost more
cost += (Double(type) / 2)
// $1/cake for extra frosting
if extraFrosting {
cost += Double(quantity)
}
// $0.50/cake for sprinkles
if addSprinkles {
cost += Double(quantity) / 2
}
return cost
}实际的视图本身很直接,我们用GeometryReader确保蛋糕的图片正确缩放,在ScrollView布局一个VStack,然后是图片,价格文本,然后是下单按钮。
我们稍后再来实现按钮的动作,首先完成基本的布局 —— 把ContentView的body属性替换成下面这样:
GeometryReader { geo in
ScrollView {
VStack {
Image("cupcakes")
.resizable()
.scaledToFit()
.frame(width: geo.size.width)
Text("Your total is $\(self.order.cost, specifier: "%.2f")")
.font(.title)
Button("Place Order") {
// place the order
}
.padding()
}
}
}
.navigationBarTitle("Check out", displayMode: .inline)这些对你来都不新鲜了,接下来是新的知识点...
我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~
