DEV Community

Cover image for Creating a Simple Watch Timer App in SwiftUI Part 1
cano0029
cano0029

Posted on • Edited on

Creating a Simple Watch Timer App in SwiftUI Part 1

(This blog post's cover photo has been designed using resources from Freepik.com)

By bringing together SwiftUI and Combine, we can create a simple watch timer app that will add a little amount of pressure to any user. Its implementation is simple, so let's get started.

This is what our app will look like at the end of this short tutorial:

Image description


Step 1: Setting Up a New watchOS Project

To create a new watchOS project:

  1. In Xcode, choose File > New > Project.
  2. Go to the watchOS tab.
  3. Choose Watch App as the template of your new project and click Next.

In the project options, enter the name for your project (I named mine "SimpleTimerApp" 😎). Ensure that the Language is set to Swift.

Once you click Next, you will be asked to select a location to save your project. Click Create.

Watch App Project Template

Project Options


Step 2: Declaring our State variables

For this, we can use the default ContentView.swift file. Let's first create our two state variables:

  1. 'count' (represents our time in seconds)
  2. 'timer' (represents our countdown timer)
@State private var count = 0
@State private var timer: Timer?
Enter fullscreen mode Exit fullscreen mode

When we use @State (a property wrapper), we’re asking SwiftUI to watch a property for changes. So if we add 1 to our count variable, for example, our State will see this and re-render the view. If there is a change in the state of this property, we will see the number on our screen update.

To make a countdown timer or similar, we will use Timer. For our needs, we'll use an optional variable to set the timer to nil at the start. Our timer will be set at a later time.


Step 3: Defining our SwiftUI Stacks

Using stacks in SwiftUI allows us to arrange our UI elements. We will need two type of stacks in this project:

  • VStack - arranges UI elements in a vertical line, i.e above or below each other.
  • HStack - arranges elements in a horizontal line, i.e. beside each other.
var body: some View {
        VStack(alignment: .center, spacing: 8){
            Text("Hello, World!")
                .padding()

            HStack(alignment: .center, spacing: 8){
                //Increment button
                //'Go' button
            }
        }
    }
Enter fullscreen mode Exit fullscreen mode

We will further customize our stacks with alignment and spacing in order to modify their appearance. For example, our VStack elements will be aligned horizontally in the middle and 8 points apart. Our _HStack _elements will be aligned vertically in the middle and 8 points apart as well.


Step 4: Preparing our UI elements 🎨

Replace the string "Hello Word" with "0" as a placeholder text for our count. We will also increase the font size and font weight of this string.

Text("\(count)")
    .font(.system(size:90))
    .fontWeight(.bold)
Enter fullscreen mode Exit fullscreen mode

Now, let's set up our two buttons in our HStack:

  1. Increment button: Adds 1 to the count variable every time the button is pressed. As a styling preference, we are rendering the plus icon from SF Symbols using Image.

  2. 'Go' button: This is where we will start our countdown timer. For styling, we will make the text a green colour.

//Increment button

Button {
  count = count + 1 
} label: {
  Image(systemName: "plus") 
    .font(.system(size:34))
}

//'Go' button

Button {
  //set our countdown timer
} label: {
   Text("Go!")
      .font(.system(size:34))
      .foregroundColor(.green)
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Starting our Countdown Timer

Once a user clicks on the 'Go!' button, we want our timer to start counting (in seconds) all the way down to 0. However, we want to ensure that our count is neither equal to nor less than 0. We will only start our timer if our count value is greater than 0.

Button {

    timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) {time in
        if count > 0 {
            count -= 1
        }else {
          timer?.invalidate()
        }
    }

}label: {
    Text("Go!")
        .font(.system(size:34))
        .foregroundColor(.green)

}
Enter fullscreen mode Exit fullscreen mode

You can destroy an existing timer by calling its **invalidate() **method. In this case, the timer is destroyed until it is restarted. If the count value was increased a second time, the timer will not begin until the user presses the 'Go' button.


Overview

And that's it! Below, you will see the finished code structure of this simple app. Thank you for reading!

import SwiftUI
import Combine

struct ContentView: View {

    @State private var count = 0
    @State private var timer: Timer?

    var body: some View {
        VStack(alignment: .center, spacing: 8){
            Text("\(count)")
                .font(.system(size:90))
                .fontWeight(.bold)

            HStack(alignment: .center, spacing: 8){

                //Increment button
                Button {
                    count = count + 1 //add 1 to the count variable each time the button is pressed
                } label: {
                    Image(systemName: "plus") //rendering an icon from SF Symbols
                        .font(.system(size:34))
                }

                Button {

                    timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) {time in
                        if count > 0 {
                            count -= 1
                        }else {
                          timer?.invalidate()
                        }
                    }

                }label: {
                    Text("Go!")
                        .font(.system(size:34))
                        .foregroundColor(.green)

                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
dan_harper_a84fdb85a37a65 profile image
Dan Harper

Wow - what a breath of fresh air to find a clean understandable example to follow - and thoughtfully explained!

Thank you!