NEW: Join my free 100 Days of SwiftUI challenge today! >>

Checking for a valid address

Paul Hudson    @twostraws   

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(destination: CheckoutView(order: order)) {
            Text("Check out")
        }
    }
}
.navigationBarTitle("Delivery details", displayMode: .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(destination: CheckoutView(order: order)) {
        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.

LEARN SWIFTUI FOR FREE I have a massive, free SwiftUI video collection on YouTube teaching you how to build complete apps with SwiftUI – check it out!

BUY OUR BOOKS
Buy Pro Swift Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift (Vapor Edition) Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!

Average rating: 5.0/5