Waldo sessions now support scripting! – Learn more
App Development

SwiftUI Picker Made Easy: Tutorial With Example

Juan Reyes
Juan Reyes
SwiftUI Picker Made Easy: Tutorial With Example
June 4, 2021
10
min read

The purpose of this article is to get you acquainted with SwiftUI Picker. In the process, we’ll be creating a sample code, customizing the appearance of our Picker, and implementing some functionality with them.

Once you have completed this article, you’ll be able to implement your own pickers in Xcode and prove the quality of your work with some basic testing procedures.

For the purpose of brevity, the tutorial that follows will assume that you have experience working with Swift and Xcode 12. If you don’t, please explore them here.

SwiftUI Workflows

If you haven’t worked on a SwiftUI project, I highly recommend reviewing it first before continuing to read this article, considering that a lot has shifted in the workflow required to develop SwiftUI projects.

Here’s a brief summary of what will be in all SwiftUI projects when creating them.

The template project holds two files: a ContentView.swift file and an <APP_NAME>App.swift file. (The APP_NAME is the name you used for the project.)

All SwiftUI View classes have similar structures. A View struct defines the view structure and functionality, and a PreviewView struct serves as a helper for the emulator to display your work in real time.

A variable of type View called “body” defines the body of the ContentView. And so any modification that you want to make to your View must be done in this variable.

This ContentView contains a TextView object with the text “Hello World!”

Now that all that is out of the way, let’s dive into our project.

Pickers on SwiftUI

Prior to adding our Picker, we need to make sure we understand State variables.

If you don’t know what a State variable is, in simple terms, it’s a variable (prefixed by @) that keeps the “state” of a property in the View. The purpose of State variables is to inform the parent View and its hierarchy of the state of dynamic elements in the View. Without them, there would be inconsistencies in the behavior of the app on navigation.

A State variable would look something like this: @State var title : String = “”.

Notice that it has been initialized. If you can’t initialize it due to the structure of your code, you should change it to a @Bind and pass to it a reference to a parent @State variable.

Excellent. Now, let’s add our Picker to the body.

SwiftUi picker screenshot 1

As you can see, the constructor is simple. It requires only an initial selection, title, and content items to display in the scroll view.


import SwiftUI
struct ContentView: View {
    var body: some View {
        Picker(selection: .constant(1),
               label: Text("Picker"),
               content: {
            Text("1").tag(1)
            Text("2").tag(2)
               })
    }
}

You can proceed to test the Picker in the previewer by pressing the play button and interacting with it. See that, despite looking plain, it has all the expected functionalities right off the bat.

Modifying Our Pickers’ Appearance

The first thing in order is to add some padding to our Picker so it doesn’t monopolize the screen width.


import SwiftUI
struct ContentView: View {
    var body: some View {
        Picker(selection: .constant(1),
               label: Text("Picker"),
               content: {
            Text("1").tag(1)
            Text("2").tag(2)
               })
            .padding()
    }
}

This code will result in the following.

SwiftUi picker screenshot 2

A very subtle change, but essential nonetheless.

NOTE: If you’re confused about why adding ‘.padding()’ to the element did the trick or what this clause is, I’d recommend reading further about SwiftUI modifiers.

Depending on the target platform you selected for your project, there are specific designs available for our Picker. In this case, we’ll be focusing on the iOS ecosystem.

The modifiers available for our Picker are the following:

  • DefaultPickerStyle: This is the default style for the development platform. In our case, this is equal to the WheelPickerStyle.
  • WheelPickerStyle: This is the most familiar wheel style where you can directly scroll and select a value.
  • InlinePickerStyle: This style presents all the options together with other views in the container.
  • MenuPickerStyle: This style presents the Picker as a button that displays the options as a pop-up for the user to pick.
  • SegmentedPickerStyle: This style presents the options horizontally next to each other and only allows one option at a time to be selected.

Here is an example:


import SwiftUI
struct ContentView: View {
    var body: some View {
        Picker(selection: .constant(1),
               label: Text("Picker"),
               content: {
            Text("1").tag(1)
            Text("2").tag(2)
               })
            .padding()
            .pickerStyle(MenuPickerStyle())
    }
}

This code would yield the following.

SwiftUi picker screenshot 3

Feel free to try them all.

Notice that, when pressed, the options are available to pick, but your selection doesn’t persist.

To solve this, we need to add some functionality to our Picker.

Functionality on Our Pickers

To reflect what value the user selects in our Picker, we first need to add a Text element below it.

To do that, let’s embed the Picker in a VStack and add a Text that will indicate the selected value. Then, let’s add a @State variable that will persist the selected value index.


import SwiftUI
struct ContentView: View {
    @State var selectionIndex: Int = 0
    var body: some View {
        VStack {
            Picker(selection: $selectionIndex,
                   label: Text("Picker"),
                   content: {
                Text("1").tag(1)
                Text("2").tag(2)
                   })
                .padding()
                .pickerStyle(MenuPickerStyle())
            Text("Selected value \(selectionIndex)")
        }
    }
}

Now when you change the selection, the text reflects the change. Simple, right?

But what if we want to have our options defined somewhere else, or we want to ensure the options are the correct type? You’ll want to have a more robust implementation with enums for this scenario and have something more like this:


import SwiftUI
struct ContentView: View {
    enum Option: String, CaseIterable, Identifiable {
        case one
        case two
        case three
        var id: String { self.rawValue }
    }
    @State var selection: Option = .one
    var body: some View {
        VStack {
            Picker(selection: $selection,
                   label: Text("Picker"),
                   content: {
                    ForEach(Option.allCases) { option in
                        Text(option.rawValue.capitalized).tag(option)
                    }
                   })
                .padding()
                .pickerStyle(MenuPickerStyle())
            Text("Selected value \(selection.rawValue)")
        }
    }
}

Nevertheless, any robust implementation requires proper testing. This case is no exception. So let’s build some simple tests to guarantee the quality of our code.

Test Your Pickers

Having a comprehensive implementation of SwiftUI Picker in your hands, it’s recommended that you build some tests to guarantee that your code is accurate and continues to work as expected. To achieve that, you can work with Xcode’s UI testing infrastructure, which comes already bundled with the project.

When you open the Test iOS group in the left bar, you’ll see a Test_iOS.swift class file. You can see that everything you need to set up a test is there for you. Now, you can continue and run it if you want to see the app running in the emulator.

Nice!

Now, to test our code, add the following to the testExample() method:


func testExample() throws {
    // UI tests must launch the application that they test.
    let app = XCUIApplication()
    app.launch()
    app.buttons.element(boundBy: 0).tap()
    let exp = expectation(description: "Test after 1 seconds")
    let result = XCTWaiter.wait(for: [exp], timeout: 1.0)
    if result == XCTWaiter.Result.timedOut {
        app.buttons.element(boundBy: 2).tap()
        XCTAssert(app.staticTexts["Selected value two"].exists)
    } else {
        XCTFail("Delay interrupted")
    }
    // Use recording to get started writing UI tests.
    // Use XCTAssert and related functions to verify your tests produce the correct results.
}

For this specific test, it’s necessary to implement an XCTWaiter, which tells the system to wait for some time to execute the next instruction in the test. This behavior is needed because the pop-up needs time to display before tapping on an option.

When running the code, you’ll notice that the Picker is successfully tapped, and the value “two” is selected.

For testing, quality is always more important than quantity. However, if you want to have a more complete and comprehensive testing workflow, you can check out Waldo and their no-code testing solution. You can also find more info on how to implement testing in Xcode UI here.

Picking Our Next Steps

Developing in the Apple ecosystem has always been one of my greatest delights. I rejoice in the experience of reaching my full potential with the tools and expertise available.

Now you have a more comprehensive understanding of the Picker element with a working example as well as more testing options to ensure your work quality. With this, you can hopefully find a similar level of productivity and enjoyment in your development journey.

Hopefully, this article helps you find the missing piece if you were lost or inspires you to create something extraordinary in your career.

Automated E2E tests for your mobile app

Waldo provides the best-in-class runtime for all your mobile testing needs.
Get true E2E testing in minutes, not months.

Reproduce, capture, and share bugs fast!

Waldo Sessions helps mobile teams reproduce bugs, while compiling detailed bug reports in real time.