Skip to content

Instantly share code, notes, and snippets.

@siracusa
Last active November 11, 2021 12:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save siracusa/4ca275d1a13200ec52c8caf2c5b7320c to your computer and use it in GitHub Desktop.
Save siracusa/4ca275d1a13200ec52c8caf2c5b7320c to your computer and use it in GitHub Desktop.

When creating and using a custom ButtonStyle for a SwiftUI button that changes its appearance based on the value of the configuration.isPressed property, that property sometimes get stuck with a "true" value, leaving the button to appear in its pressed state even when it is not being pressed. It stays stuck this way until the button is clicked again.

A sample project is attached. It's also in a public repo at https://github.com/siracusa/SwiftUIButtonIsPressedStateBug

To reproduce the bug:

  1. Build and launch the sample project. A window should appear showing a large button view with a green background and some white text that says "Hello".

  2. Click button view a few times and confirm that background color turns blue on mouse-down and switches back to green on mouse-up.

  3. Grab a text file from the Finder and drag it on top of the green window, but don't release the mouse button. The text "Hello" should turn red when the file is over the button, and the button background should remain green.

  4. While still holding down the mouse button to continue the drag operation, drag the file into and out of the button window. You will need to do this many times, pausing while holding the file over the button for a moment or two every few attempts.

Expected results:

The button view background remains green during the entire drag operation, because at no point during the drag operation is the button "pressed."

Actual results:

Eventually, the button background will turn blue, and then it will stay blue until the button is clicked normally.

See video for a reproduction demonstration: https://twitter.com/siracusa/status/1458456061032140802

@gualtierofrigerio
Copy link

I was able to reproduce it.
If you pass highlighted as a parameter to AppButtonStyle you'll force makeBody to be called every time that value changes, so each time you drag a file over the View.
Almost all the times, configuration.isPressed is false when highlighted changes, so it behaves correctly.
The few times it doesn't work, you can see isPressed is true, and as a workaround you can also check if highlighted is false so you don't set the blue color while dragging.
In my tests, using highlighted mitigated the bug even without checking when setting the the color, maybe evaluating makeBody more frequently doesn't trigger the bug or makes it even harder to reproduce.

@siracusa
Copy link
Author

siracusa commented Nov 10, 2021

@gualtierofrigerio: I get a slightly different bug when I both pass highlighted to AppButtonStyle() and also check if it's false in the .background() condition: I can get the background to turn blue, but it switches back to green when the dragged item is over the button. Moving the dragged item out of the window switches it back to blue.

Do you have workaround code that never turns the window blue during an ongoing drag?

In the end, I'm not sure how to work around the fact that configuration.isPressed is true even when the button is not being pressed. I can't even check if the mouse button is down because it will be down during a drag operation as well.

@gualtierofrigerio
Copy link

I couldn't reproduce the behaviour you're describing. In theory, if highlighted is set to true in dropEntered you should never see the blue background if you only set if when highlighted is false and isPressed is true.

.background(configuration.isPressed && highlighted == false ? Color.blue : Color.green)

That's the only way I can get around the fact that isPressed is set to true during a drag.
As you say, the mouse is down during the drag so you can't check that.
I don't think there is anything wrong in your code, it has to be some weird bug that sets the isPressed value at the wrong time.

@siracusa
Copy link
Author

Yeah, even with that exact code, I can still get it behave badly, as I described above. Here's a video of it happening: http://siracusafamily.org/tmp/FB9750689/demo-gualtierofrigerio-1.mov

@gualtierofrigerio
Copy link

Weird. So if you print both isPressed and highlighted at the beginning of makeBody you end up in a scenario where highlighted is false even if the drag is inside your View. Tomorrow I'm going to try with another project of mines where I have drag&drop on the Mac to see if is happens at all. By the way, I'm on 12.0.1

@siracusa
Copy link
Author

Yeah, here's a log line from when the bug is triggered:

2021-11-10 16:19:32.096223-0500 SwiftUIButtonIsPressedStateBug[12680:2397379] isPressed: true, highlighted: false

@gualtierofrigerio
Copy link

I don't know why I can hardly reproduce it on my Mac especially if I pass highlighted to AppButtonStyle. You seem to have found an easier way to reproduce it, but trying to mimic what you do in the video doesn't help me.
However, after watching the video again I understood what is going on, isPressed is wrongly set to true, as long as you are dragging into your view the highlighted is true but once you move outside you get the call to dropExited so highlighted is false and the background turns blue.
Didn't think about that, and I guess it will be more complex to solve. You'd have to pass something back to the ContentView from AppButtonStyle, so every time makeBody gets evaluated you know if isPressed is true, and you can keep some sort of state on ContentView (or a ViewModel) so you know that the user was dragging, got inside your view, isPressed was wrongly set, then got outside and you don't have to turn the background blue.
I guess handling this internal state could be bug prone too... so I'm not sure it is worth the hassle.
My only concern is that I'm having an hard time reproducing the bug on my Mac, if it happens to the Apple developer that will take care of the radar he may eventually give up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment