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

Creating pickers in a form

Paul Hudson    @twostraws   

SwiftUI’s pickers serve multiple purposes, and exactly how they look depends on which device you’re using and the context where the picker is used.

In our project we have a form asking users to enter how much their check came to, and we want to add a picker to that so they can select how many people will share the check.

Pickers, like text fields, need a two-way binding to a property so they can track their value. We already made an @State property for this purpose, called numberOfPeople, so our next job is to loop over all the numbers from 2 through to 99 and show them inside a picker.

Modify the first section in your form to include a picker, like this:

Section {
    TextField("Amount", value: $checkAmount, format: .currency(code: Locale.current.currency?.identifier ?? "USD"))
        .keyboardType(.decimalPad)

    Picker("Number of people", selection: $numberOfPeople) {
        ForEach(2..<100) {
            Text("\($0) people")
        }
    }
}

Now run the program in the simulator and try it out – what do you notice?

Hopefully you spot several things:

  1. There’s a new row that says “Number of people” on the left and “4 people” on the right.
  2. There’s are two gray arrows on the right edge, which is the iOS way of signaling that tapping the row shows a menu of options.
  3. The row says “4 people”, but we gave our numberOfPeople property a default value of 2.

So, it’s a bit of “two steps forward, two steps back” – we have a nice result, but it doesn’t work and doesn’t show the right information!

We’ll fix both of those, starting with the easy one: why does it say 4 people when we gave numberOfPeople the default value of 2? Well, when creating the picker we used a ForEach view like this:

ForEach(2 ..< 100) {

That counts from 2 up to 100, creating rows. What that means is that our 0th row – the first that is created – contains “2 People”, so when we gave numberOfPeople the value of 2 we were actually setting it to the third row, which is “4 People”.

So, although it’s a bit brain-bending, the fact that our UI shows “4 people” rather than “2 people” isn’t a bug.

Pickers come with lots alternative styles depending on how you want things to behave. For example, later we'll use a segmented style for the tip percentage picker, which is a good fit because it doesn't have many options.

One popular picker style is called navigation link, which moves the user to a new screen to select their option. To try it here, add the .pickerStyle(.navigationLink) modifier to your picker, like this:

Picker("Number of people", selection: $numberOfPeople) {
    ForEach(2 ..< 100) {
        Text("\($0) people")
    }
}
.pickerStyle(.navigationLink)

That won't quite work as expected, though. This time you'll see a gray disclosure indicator on the right edge, but the whole row will be grayed out – it won't show anything when tapped.

What SwiftUI wants to do – which is also why it’s added the gray disclosure indicator on the right edge of the row – is show a new view with the options from our picker.

To do that, we need to add a navigation stack, which does two things: gives us some space across the top to place a title, and also lets iOS slide in new views as needed.

So, directly before the form add NavigationStack {, and after the form’s closing brace add another closing brace. If you got it right, your code should look like this:

var body: some View {
    NavigationStack {
        Form {
            // everything inside your form
        }
    }
}

If you run the app again you’ll see that tapping on the Number Of People row makes a new screen slide in with all the other possible options to choose from.

You should see that “4 People” has a checkmark next to it because it’s the selected value, but you can also tap a different number instead – the screen will automatically slide away again, taking the user back to the previous screen with their new selection.

What you’re seeing here is the importance of what’s called declarative user interface design. This means we say what we want rather than say how it should be done. We said we wanted a navigation link picker with some values inside, but we didn't have to say "now create a list of all our items, showing a checkmark to whichever is selected right now."

Do you prefer the menu picker, or the navigation link picker? It's your app – you get to choose! I'm going to go with the menu picker as that's the default, but please go with whatever you prefer.

Before we’re done with this step, let’s add a title to that new navigation bar. Give the form this modifier:

.navigationTitle("WeSplit")

Tip: It’s tempting to think that modifier should be attached to the end of the NavigationStack, but it needs to be attached to the end of the Form instead. The reason is that navigation stacks are capable of showing many views as your program runs, so by attaching the title to the thing inside the navigation stack we’re allowing iOS to change titles freely.

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

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.