NEW: Subscribe to Hacking with Swift+ and accelerate your learning! >>

Ending the app with allowsHitTesting()

Paul Hudson    @twostraws   

SwiftUI lets us disable interactivity for a view by setting allowsHitTesting() to false, so in our project we can use it to disable swiping on any card when the time runs out by checking the value of timeRemaining.

Start by adding this modifier to the innermost ZStack – the one that shows our card stack:

.allowsHitTesting(timeRemaining > 0)

That enables hit testing when timeRemaining is 1 or greater, but sets it to false otherwise because the user is out of time.

The other outcome is that the user flies through all the cards correctly, and ends with none left. When the final card goes away, right now our timer slides down to the center of the screen, and carries on ticking. What we want to happen is for the timer to stop so users can see how fast they were, and also to show a button allowing them to reset their cards and try again.

This takes a little thinking, because just setting isActive to false isn’t enough – if the app moves to the background and returns isActive will be re-enabled even though there are no cards left.

Let’s tackle it piece by piece. First, we need a method to run to reset the app so the user can try again, so add this to ContentView:

func resetCards() {
    cards = [Card](repeating: Card.example, count: 10)
    timeRemaining = 100
    isActive = true
}

Second, we need a button to trigger that, shown only when all cards have been removed. Put this after the innermost ZStack, just below the allowsHitTesting() modifier:

if cards.isEmpty {
    Button("Start Again", action: resetCards)
        .padding()
        .background(Color.white)
        .foregroundColor(.black)
        .clipShape(Capsule())
}

Now we have code to restart the timer when resetting the cards, but now we need to stop the timer when the final card is removed – and make sure it stays stopped when coming back to the foreground.

We can solve the first problem by adding this to the end of the removeCard(at:) method:

if cards.isEmpty {
    isActive = false
}

As for the second problem – making sure isActive stays false when returning from the background – we should just update our function attached to willEnterForegroundNotification so that it explicitly checks for cards:

.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
    if self.cards.isEmpty == false {
        self.isActive = true
    }
}

Done!

Hacking with Swift is sponsored by NSSpain

SPONSORED Announcing NSSpain 2020: Remote Edition! An online, continuous conference for iOS developers. We’ll start on Thursday and finish on Friday, with talks, activities, and lots of fun for 36 hours, non-stop. Sound good? Join us!

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift 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 (Vapor Edition) 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 Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.8/5

Link copied to your pasteboard.