How to disable a Button from a repeat selection until its animation completes?

I’ve produced a game using SwiftUI for the interface. It works as intended; i.e. play requires some reflection and then an input using buttons; only legal buttons are enabled so illegal input should be impossible. An animation is played when input is received and this is causing a problem. I discovered that if I play very quickly, tapping buttons without reflection, it seems I can overtake the animation and press buttons that should no longer be available effectively giving rise to an illegal move.
My question is, is there some way of locking out the interface as soon as input is received and only unlocking it after input has been processed and the interface update (which is almost instantaneous)? I’ve looked at NSLock and async/await but don’t see how they might be applied here.

These seem to be the relevant parts of my code with the animation commented out. When I apply the animation, I get the problem, so it seems to me that it is during the animation that I’m exposed to further button presses:

var action: (Int, Int) -> () {
   if viewModel.nextGrid == nil {
      return { r, c in
         viewModel.selectNextGrid(Coordinate(r,c))
      }
   } else {
      return { r, c in
         viewModel.play(cell: Coordinate(r,c))
      }
   }
}

var body: some View {
   VStack {
      ForEach((0...2), id: \.self) { r in
         HStack {
            ForEach((0...2), id: \.self) { c in
               let game = source(coord: Coordinate(r,c))
               if !game.state.isEmpty() {
                  Text(game.state.rawValue)
               } else {
                  Button {
//                        withAnimation {
//                           rotate += 180
                        action(r,c)
//                        }
                  } label: {
                     image
                        .resizable()
//                           .rotation3DEffect(.degrees(rotate), axis: (x:1,y:1,z:0))
                  }
               }
            }
         }
      }
   }
}

There are (at least) two possible solutions — but honestly I don’t like them much:

  1. Manually set an animation duration, and use a flag to skip the processing in the button handler (or whatever other place you need that to be done). You set that flag to true when the handler is executed the first time, and reset it after the animation duration (using either dispatch async or Task.sleep)
  2. You save the rotate final value to a temporary property, then in the button handler you check if the current rotation value is different than the final value (which means the animation is in progress), in which case you do an early return. Not tested a solution like this, but intuitively it should work.

A third possible solution is to use the .disabled() modifier — this should disable user interaction on that view and all its children, but be aware that this can be overridden by parent views.

Thanks, I like your third solution best and have actually tried it since I wrote, with some success. Refining it right now and I’ll write about how I get on

Thanks again @jeden but it turns out that I managed to correct the overlap in code in a simple way and the locks were doing nothing.
The animation runs in a separate thread and subsequent button taps seem to just queue up and get animated anyway. What was happening before was that if an illegal button was still in the display, it tried to execute the action and I have trapped this later during execution, but a series of moves I haven’t had time to see are being executed if they are legal.

So the problem remained. Swifts animation is running along independently of my game and I wasn’t able to implement your other suggestions. I set an animation duration but I don’t know what you mean by the handler. I think you mean action() but it runs in parallel with the animation, finishes before it and carries on. I tried Task.sleep but I got tied up in errors about what throws, asyncs and awaits and never unravelled them :frowning_face:

AND THEN I FOUND THIS EXCELLENT SOLUTION ON STACK OVERFLOW:

That’s definitely THE solution, a lot more elegant and clean