UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

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 property and preview that AddressView has:

struct CheckoutView: View {
    var order: Order

    var body: some View {
        Text("Hello, World!")
    }
}

#Preview {
    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 properties in Order to store delivery details:

var name = ""
var streetAddress = ""
var city = ""
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("Check out") {
            CheckoutView(order: order)
        }
    }
}
.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.

That code will throw up lots of errors, but it takes just one small change to fix them – change the order property to this:

@Bindable var order: Order

Previously you've seen how Xcode lets us bind to local @State properties just fine, even when those properties are classes using the @Observable macros. That works because the @State property wrapper automatically creates two-way bindings for us, which we access through the $ syntax – $name, $age, etc.

We haven't use @State in AddressView because we aren't creating the class here, we're just receiving it from elsewhere. This means SwiftUI doesn't have access to the same two-way bindings we'd normally use, which is a problem.

Now, we know this class uses the @Observable macro, which means SwiftUI is able to watch this data for changes. So, what the @Bindable property wrapper does is create the missing bindings for us – it produces two-way bindings that are able to work with the @Observable macro, without having to use @State to create local data. It's perfect here, and you'll use it a lot in your future projects.

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 local state, then any address details we had entered would disappear if we moved back to the original view.

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("Check out") {
        CheckoutView(order: order)
    }
}
.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.

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI 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 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 Beyond Code

Was this page useful? Let us know!

Average rating: 4.8/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.