Why is SwiftUI so fun? (part 2)

Why is SwiftUI so fun? (part 2)

This is the second part of my dive into SwiftUI and what makes it in my opinion the best way to create a mobile app.

ยท

5 min read

Introduction

Last week I published a blog post about why I like SwiftUI. I hope you had the chance to give it a read (if not, here it is :D). We basically built a simple watchOS counter app with the purpose of showcasing just how easy it is to grasp the basics of SwiftUI, especially if you come from a web development background like me.

Well, in today's blog post we will be expanding on last week's post, and we will be adding a timer to our watchOS app in the most simple way possible.

Our app will work like this:

  • We can increment the counter with the + icon
  • When we tap Go, the counter will start counting down by 1 every second.
  • When counter reaches 0, it will stop.

ezgif.com-gif-maker (4).gif

Prerequisites

Implementing the Timer

We want to use some Swift magic so that every second, we will run a piece of code that will decrement the counter. In JavaScript, you'd be doing this using a setInterval method. In SwiftUI, we have the same approach, but it might look a bit different.

To get this done, we'll be using the Timer class that is already build into Swift.

Let's declare our timer state variable like you see below:

@State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

I know, this looks a bit odd. We can probably figure out that we're running this every: 1 second, but what about the other arguments, what do does mean? Let's break them all down:

  • As we just mentioned, every: 1 basically means that you want some stuff to happen every second
  • on: .main is how we tell Swift that we want our main UI to be updated as state changes
  • in: .common will allow the timer to run alongside other common tasks in the app such as scrolling.
  • autoconnect() means that we will initialize the timer as soon as the main component loads.

Ok, so now that we have the Timer foundation in place and we understand how it works, it's time to actually do something with it. To make use of our timer state variable, we need to use the onRecieve modifier on any UI element that we want to change every second.

On the Text UI Element from the last week's code, we are going to pass in our timer state variable to the onReceive modifier, and we will (for now) just decrement our counter state variable by 1 every second. If the counter is 0 or less, we will set the counter to 0 every second ๐Ÿ˜ฌ - we will solve this later.

@State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

...

Text("\(count)")
    .font(.system(size: 90))
    .fontWeight(.black)
    .multilineTextAlignment(.center)
    .onReceive(timer) { _ in
        count = count > 0 ? count - 1 : 0
     }

Great, we will come back to address some issues here, but for now we will move on to figuring out how to trigger this .onReceive modifier.

The plan is to start the countdown timer as soon as we press on the Go! button, so all we will need to do is assign our timer state variable a new instance of the Timer built-in class with the same arguments mentioned earlier. It will look like this:

Button {
    timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    } label: {
        Text("Go!")
        .font(.system(size: 34))
    }

Awesome job. At a surface level, if you would build the app now, it will run ok, however there are some pretty important performance issues that we need to work on before we finish.

Dealing with problems

Okay, let's first see what the problems are in our code, then we can go ahead and see how we can tackle them one by one:

  • As mentioned above, when our counter hits 0, we will set the count to 0 every second. We are never actually stopping the Timer.
  • If we never increment the counter, we can still press on the Go! button.

For the first issue we have, our onReceive modified needs a bit of validation, and we also need to cancel our Timer as part of that validation. Basically, we will check if the counter is equal to 0, and if that is true, we will cancel the Timer like so: timer.upstream.connect().cancel()

Here is how our updated onReceive modified looks like:

.onReceive(timer) { _ in
    if count == 0 {
        timer.upstream.connect().cancel()
    }
    count = count > 0 ? count - 1 : 0
}

Now for the last adjustment, we want to allow the button to be pressed only if the counter is greater than 0, and this can be done very easily by adding the disabled modifier to the Go! button. Our code will look like this:

 Button {
    timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
} label: {
    Text("Go!")
    .font(.system(size: 34))
}
.disabled(count == 0)

If you need to have a look at the whole code block, feel free to have a look at the gist here.

Closing Notes

In this final part of the SwiftUI blog posts, I hope I was able to make you a little more excited about the things you can do with SwiftUI super easily. While my expertise is in web development, I find it incredibly inviting to start developing apps with SwiftUI and I am looking forward to dive even deeper into SwiftUI in a future blog post.

See you on the next one โœŒ๐Ÿผ

ย