[SwiftUI 100 天] Cupcake Corner - part4 表单校验

831 阅读5分钟
译自 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 = ""

然后把AddressViewbody替换成下面这样:

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,然后是图片,价格文本,然后是下单按钮。

我们稍后再来实现按钮的动作,首先完成基本的布局 —— 把ContentViewbody属性替换成下面这样:

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及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~