SwiftKickMobile / SwiftMessages

A very flexible message bar for UIKit and SwiftUI.
MIT License
7.24k stars 741 forks source link

SwiftMessages disable buttons tap event when show with presented view #512

Closed ajay-simform closed 1 year ago

ajay-simform commented 1 year ago

https://user-images.githubusercontent.com/47667077/203799756-578c4fa7-83b0-4708-9270-3d81807ba0bd.MP4

SwiftMessageDemo.zip

wtmoose commented 1 year ago

This is an iOS bug with modals unrelated to SwiftMessages. I discovered the same issue a couple of months ago and filed a ticket with Apple.

What is happening is that the tap gesture is being shifted up. Try tapping a little bit below the button and you'll see that it is still tappable.

To demonstrate, the issue can be reproduced with this trivial SwiftUI view:

struct ContentView: View {
    @State var isPresented = false

    var body: some View {
        VStack {
            Button("Hello") { isPresented = true }
            ScrollView {
            }
        }
        .sheet(isPresented: $isPresented) {
            Text("World")
        }
    }
}
  1. Tap "Hello" to show the modal
  2. Switch away from the app and back
  3. Dismiss the modal
  4. Tap "Hello" again

Same problem. Tapping "Hello" doesn't work. Tapping below "Hello" works. Here's the sample project.

TapTargetFail.zip

wtmoose commented 1 year ago

I do have a workaround though. I'm hesitant to put this into SwiftMessages because I'm not sure how robust it is. I can say that it is being used in a large scale app and there haven't been any issues.

The idea of the workaround is to mess with the key window after the modal is dismissed. To do this, I added this .onDismiss modifier to presentedView:

struct presentedView: View {
    var body: some View {
        ...
        .onDisappear {
            // UIWindow.keyWindow is a custom property we use
            guard let keyWindow = UIWindow.keyWindow else { return }
            let window = UIWindow()
            let vc = UIViewController()
            window.rootViewController = vc
            vc.loadViewIfNeeded()
            vc.view.backgroundColor = .clear
            window.windowScene = keyWindow.windowScene
            window.makeKeyAndVisible()
            keyWindow.makeKeyAndVisible()
        }
    }
}

extension UIWindow {
    static var keyWindow: UIWindow? {
        return UIApplication.shared.connectedScenes
            .sorted { $0.activationState.sortPriority < $1.activationState.sortPriority }
            .compactMap { $0 as? UIWindowScene }
            .compactMap { $0.windows.first { $0.isKeyWindow } }
            .first
    }
}

private extension UIScene.ActivationState {
    var sortPriority: Int {
        switch self {
        case .foregroundActive: return 1
        case .foregroundInactive: return 2
        case .background: return 3
        case .unattached: return 4
        @unknown default: return 5
        }
    }
}

The keyWindow extension property was copied from SwiftMessages. What the workaround does is to quickly add and remove a transparent window after the modal is dismissed. There is no visible effect, but the process somehow fixes the corrupted tap handling.

Here's your sample project with the workaround applied:

SwiftMessageDemoWorkaround.zip

wtmoose commented 1 year ago

Closing. Reopen if you need further help.