NEW: Nominations are now open for the 2019 Swift Community Awards! >>

Building a basic layout

Paul Hudson    @twostraws   

This app is going to allow user input with a date picker and two steppers, which combined will tell us when they want to wake up, how much sleep they usually like, and how much coffee they drink.

So, please start by adding three properties that let us store the information for those controls:

@State private var wakeUp = Date()
@State private var sleepAmount = 8.0
@State private var coffeeAmount = 1

Inside our body we’re going to place three sets of components wrapped in a VStack and a NavigationView, so let’s start with the wake up time. Replace the default “Hello World” text view with this:

NavigationView {
    VStack {
        Text("When do you want to wake up?")
            .font(.headline)

        DatePicker("Please enter a time", selection: $wakeUp, displayedComponents: .hourAndMinute)
            .labelsHidden()

        // more to come
    }
}

Because we’re in a VStack, that will render the date picker as a spinning wheel on iOS, which is fine here. We’ve asked for .hourAndMinute configuration because we care about the time someone wants to wake up and not the day, and with the labelsHidden() modifier we don’t get a second label for the picker – the one above is more than enough.

Next we’re going to add a stepper to let users choose roughly how much sleep they want. By giving this thing an in range of 4...12 and a step of 0.25 we can be sure they’ll enter sensible values, but we can combine that with the %g string interpolation specifier so we see numbers like “8” and not “8.000000”.

Add this code in place of the // more to come comment”

Text("Desired amount of sleep")
    .font(.headline)

Stepper(value: $sleepAmount, in: 4...12, step: 0.25) {
    Text("\(sleepAmount, specifier: "%g") hours")
}

Finally we’ll add one last stepper and label to handle how much coffee they drink. This time we’ll use the range of 1 through 20 (because surely 20 coffees a day is enough for anyone?), but we’ll also display one of two labels inside the stepper to handle pluralization better. If the user has set a coffeeAmount of exactly 1 we’ll show “1 cup”, otherwise we’ll use that amount plus “cups”.

Add these inside the VStack, below the previous views:

Text("Daily coffee intake")
    .font(.headline)

Stepper(value: $coffeeAmount, in: 1...20) {
    if coffeeAmount == 1 {
        Text("1 cup")
    } else {
        Text("\(coffeeAmount) cups")
    }
}

The final thing we need is a button to let users calculate the best time they should go to sleep. We could do that with a simple button at the end of the VStack, but to spice up this project a little I want to try something new: we’re going to add a button directly to the navigation bar.

First we need a method for the button to call, so add an empty calculateBedtime() method like this:

func calculateBedtime() {
}

Now we need to use the navigationBarItems() modifier to add a trailing button to the navigation view. “Trailing” in left-to-right languages like English means “on the right”, and you can provide any view here – if you want several buttons, you could use a HStack, for example. While we’re here, we might as well also use navigationBarTitle() to put some text at the top.

So, add these modifiers to the VStack:

.navigationBarTitle("BetterRest")
.navigationBarItems(trailing:
    // our button here
)

In our case we want to replace that comment with a “Calculate” button. Previously I explained that buttons come in two forms:

Button("Hello") {
    print("Button was tapped")
}

Button(action: {
    print("Button was tapped")
}) {
    Text("Hello")
}

We could use the first option here, if we wanted:

Button("Calculate") {
    self.calculateBedtime()
}

That would work fine, but I’d like you reconsider. That code creates a new closure, and the closure’s sole job is to call a method. Closures are, for the most part, just functions without a name – we assign them directly to something, rather than having them as a separate entity.

So, we’re creating a function that just calls another function. Wouldn’t it be better for everyone if we could skip that middle layer entirely?

Well, we can. What the button cares about is that its action is some sort of function that accepts no parameters and sends nothing back – it doesn’t care whether that’s a method or a closure, as long as they both follow those rules.

As a result, we can actually send calculateBedtime directly to the button’s action, like this:

Button(action: calculateBedtime) {
    Text("Calculate")
}

Now, when people see that they often think I’ve made a mistake. They want to write this instead:

Button(action: calculateBedtime()) {
    Text("Calculate")
}

However, that code won’t work and in fact means something quite different. If we add the parentheses after calculateBedtime it means “call calculateBedtime() and it will send back to the correct function to use when the button is tapped.” So, Swift would require that calculateBedtime() returns a closure to run.

By writing calculateBedtime rather than calculateBedtime() we’re telling Swift to run that method when the button is tapped, and nothing more; it won’t return anything that should then be run.

Swift really blurs the lines between functions, methods, closures, and even operators (+, -, and so on), which is what allows us to use them so interchangeably.

So, the whole modifier should look like this:

.navigationBarItems(trailing:
    Button(action: calculateBedtime) {
        Text("Calculate")
    }
)

That won’t do anything yet because calculateBedtime() is empty, but at least our UI is good enough for the time being.

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!

MASTER SWIFT NOW
Buy Testing Swift Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift 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