-1

I have a simple app that is intended to work like this:

  • App loads with a default image
  • Once a tap is received a random image is loaded from list
  • If no tap is received for 5 seconds we reset back to default image

I'm attempting to accomplish this using DispatchQueue and DispatchWorkItem. I have managed to get the above functionality working - except when a user is tapping multiple times.

I'm assuming this is because we are initiating the "resetToOff" DispatchWorkItem each time we tap without resetting the 5 seconds.

How do I reset the image back to the default image if there is no touch received for 5 seconds while resetting the DispatchQueue?

Here is what I have so far:

import SwiftUI



struct PlayView : View {
@ObservedObject var viewRouter: ViewRouter
@State var imageName : String = "smiley"

var body: some View {

    ZStack {
        Color.black
        .edgesIgnoringSafeArea(.all)

        Image(imageName)
    }

    .gesture(
        TapGesture()
            .onEnded {
                let resetToOff = DispatchWorkItem {
                    self.imageName = "smiley"
                }

                self.changeImage()

        DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5), execute: resetToOff)
            }
    )
    // Activate the options menu
    .onLongPressGesture(minimumDuration: 3) {
            self.viewRouter.currentPage = "menuView"
    }
 }

 func changeImage() {
    let tempImageName : String = self.imageName

        let list : Array = [
            "smileyPink",
            "smileyGreen",
            "smileyRed",
            "smileyBlue",
            "smileyYellow"
        ]

        self.imageName = list.randomElement() ?? ""
 // Ensure that new image selection is not the same as previous image
        while tempImageName == self.imageName {
            self.imageName = list.randomElement() ?? ""
            }
    }
 }

    struct PlayView_Previews : PreviewProvider {
    static var previews: some View {
        PlayView(viewRouter: ViewRouter())
    }
}

Any help with this would be much appreciated.

2 Answers2

0

I think it's really easy to modify your code so that resetToOff only does something if 5 or more seconds passed from last tap:

var lastTapped: DispatchTime
...

.gesture(
    TapGesture()
        .onEnded {
             lastTapped = DispatchTime.now() // remember the time of last tap

and then:

let resetToOff = DispatchWorkItem {
                if self.lastTapped + .seconds(5) <= DispatchTime.now() { // 5 sec passed from last tap
                    self.imageName = "smiley"
                } // otherwise do nothing
            }

Since both setting and modifying lastTapped happens on main thread, it's thread safe. Of course it means you potentially adding "no work needed" item to main queue, but it's likely a very low impact.

timbre timbre
  • 12,648
  • 10
  • 46
  • 77
-1

It would be simpler to use a Timer, because that is something that is easy to cancel (invalidate) and start the timer again (Timer.scheduledTimer) if the user taps before the Timer fires at the end of 5 seconds.

For example, that is how my LinkSame app works. There is a 10-second "move" timer. If the user doesn't make a valid move within 10 seconds of the previous move, the user loses 10 points and the timer starts over. If the user does make a valid move within 10 seconds of the previous move, the user gets a score based on where we are in the timer and the timer starts over. That is all accomplished with a Timer.

matt
  • 515,959
  • 87
  • 875
  • 1,141