The second step in our project will be to let the user enter their address into a form, but as part of that we’re going to add some validation – we only want to proceed to the third step if their address looks good.
We can accomplish this by adding a Form
view to the AddressView
struct we made previously, which will contain four text fields: name, street address, city, and zip. We can then add a NavigationLink
to move to the next screen, which is where the user will see their final price and can check out.
To make this easier to follow, we’re going to start by adding a new view called CheckoutView
, which is where this address view will push to once the user is ready. This just avoids us having to put a placeholder in now then remember to come back later.
So, create a new SwiftUI view called CheckoutView
and give it the same Order
observed object property and preview that AddressView
has:
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())
}
}
Again, we’ll come back to that later, but first let’s implement AddressView
. Like I said, this needs to have a form with four text fields bound to four properties from our Order
object, plus a NavigationLink
passing control off to our check out view.
First, we need four new @Published
properties in Order
to store delivery details:
@Published var name = ""
@Published var streetAddress = ""
@Published var city = ""
@Published var zip = ""
Now replace the existing body
of AddressView
with this:
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 {
CheckoutView(order: order)
} label: {
Text("Check out")
}
}
}
.navigationTitle("Delivery details")
.navigationBarTitleDisplayMode(.inline)
As you can see, that passes our order
object on one level deeper, to CheckoutView
, which means we now have three views pointing to the same data.
Go ahead and run the app again, because I want you to see why all this matters. Enter some data on the first screen, enter some data on the second screen, then try navigating back to the beginning then forward to the end – that is, go back to the first screen, then click the bottom button twice to get to the checkout view again.
What you should see is that all the data you entered stays saved no matter what screen you’re on. Yes, this is the natural side effect of using a class for our data, but it’s an instant feature in our app without having to do any work – if we had used a struct, then any address details we had entered would disappear if we moved back to the original view. If you really wanted to use a struct for your data, you should follow the same struct inside class approach we used back in project 7; it’s certainly worth keeping it in mind when you evaluate your options.
Now that AddressView
works, it’s time to stop the user progressing to the checkout unless some condition is satisfied. What condition? Well, that’s down to us to decide. Although we could write length checks for each of our four text fields, this often trips people up – some names are only four or five letters, so if you try to add length validation you might accidentally exclude people.
So, instead we’re just going to check that the name
, streetAddress
, city
, and zip
properties of our order aren’t empty. I prefer adding this kind of complex check inside my data, which means you need to add a new computed property to Order
like this one:
var hasValidAddress: Bool {
if name.isEmpty || streetAddress.isEmpty || city.isEmpty || zip.isEmpty {
return false
}
return true
}
We can now use that condition in conjunction with SwiftUI’s disabled()
modifier – attach that to any view along with a condition to check, and the view will stop responding to user interaction if the condition is true.
In our case, the condition we want to check is the computed property we just wrote, hasValidAddress
. If that is false, then the form section containing our NavigationLink
ought to be disabled, because we need users to fill in their delivery details first.
So, add this modifier to the end of the second section in AddressView
:
.disabled(order.hasValidAddress == false)
The code should look like this:
Section {
NavigationLink {
CheckoutView(order: order)
} label: {
Text("Check out")
}
}
.disabled(order.hasValidAddress == false)
Now if you run the app you’ll see that all four address fields must contain at least one character in order to continue. Even better, SwiftUI automatically grays out the button when the condition isn’t true, giving the user really clear feedback when it is and isn’t interactive.
SAVE 50% To celebrate WWDC23, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.
Link copied to your pasteboard.