quanshousio / ToastUI

A simple way to show toast in SwiftUI.
MIT License
578 stars 45 forks source link

dismissAfter not dismissing on iOS13 #6

Closed LucasCarioca closed 4 years ago

LucasCarioca commented 4 years ago

Pre-requisites:

I am trying to switch from my basic alerts to using ToastUI but I'm running into an issue. For some reason my toast won't dismiss even after the set dismissAfter time. Funny enough this is only an issue when running on a device with iOS <14. Seems like the dismissAfter functionality only works with the beta.

Expected Behavior

Should dismiss after the pre-defined time.

Current Behavior

Toast stays on the screen and dismiss is never called.

Possible Solution

Steps to Reproduce (for bugs)

    @State var showWarning = false // this is set to true when a warning is triggered
    var body: some View {
        VStack {
             ...
        }.toast(isPresented: $showWarning, dismissAfter: 5.0, onDismiss: {
            print("Dismissing") // this never prints and the toast never goes away.
        }) {
            ToastView(self.messageContent).toastViewStyle(WarningToastViewStyle())
        }
    }

Context

Your Environment

quanshousio commented 4 years ago

Although I couldn't exactly reproduce your bug on iOS 13.6, I came across another bug that also related to dismissing toast. I'm currently investigating and will push a fix later. Thanks for your report.

For temporary workaround, you can try using toast() without ToastView or view that uses cocoaBlur().

LucasCarioca commented 4 years ago

I did some debugging locally with the ToastUI code and isolated the issue to this:

    let keyWindow = UIApplication.shared.windows.first { $0.isKeyWindow }
    var rootViewController = keyWindow?.rootViewController
    ...
    let toastAlreadyPresented = rootViewController is ToastViewHostingController<QTContent>

It never evaluated to true when running on my iOS 13 device but does as expected on iOS 14 Simulator and Device.

LucasCarioca commented 4 years ago

I also tried adding the dismiss here and it does dismiss the toast but the app crashes right after.

        if let dismissAfter = dismissAfter {
          DispatchQueue.main.asyncAfter(deadline: .now() + dismissAfter) {
            self.isPresented = false
          }
        }
LucasCarioca commented 4 years ago

I also tried without the ToastView like you suggested and its the same issue. The root cause is in the modifier so that's called with or without the view. I could roll my own modifier for it which I may do just to get this out but I was trying to see if I could help resolve this.

LucasCarioca commented 4 years ago

Actually some more digging and Its caused by the .onPreferenceChange(...) not kicking off when the isPresented value changes.

ToastViewModifier.swift: ToastViewIsPresentedModifier: body

internal func body(content: Content) -> some View {
    content
      .preference(key: ToastViewPreferenceKey.self, value: isPresented)
      .onPreferenceChange(ToastViewPreferenceKey.self) {
        self.present($0)
      }
  }
quanshousio commented 4 years ago

It never evaluated to true when running on my iOS 13 device but does as expected on iOS 14 Simulator and Device.

Can you set a breakpoint at that expression after the toast is showing and po rootViewController? The debugger should outputs something like this:

(lldb) po rootViewController
▿ Optional<UIViewController>
  ▿ some : <_TtGC7SwiftUI19UIHostingControllerV17ToastUISample_iOS11ContentView_: 0x7fef5e511970>

That expression simply checks if the topmost view controller is currently our UIHostingController showing the toast or not, and it should evaluates to true in that case.

I also tried adding the dismiss here and it does dismiss the toast but the app crashes right after.

I'm not understand what you meant here as it's identical to what we're having right now https://github.com/quanshousio/ToastUI/blob/42c4682363bfe8a762967e460978230df27d5c32/Sources/ToastUI/ToastViewModifier.swift#L42-L46

I also tried without the ToastView like you suggested and its the same issue. The root cause is in the modifier so that's called with or without the view. I could roll my own modifier for it which I may do just to get this out but I was trying to see if I could help resolve this.

Thank you for your information. I'm still trying to figure out what's the culprit here. It seems the app crashes right away when the toast dismisses due to the invalid usage of animating the blurred effect. I just pushed a fix on hotfix-1.0.2 branch, can you check that out to see if it works?

Actually some more digging and Its caused by the .onPreferenceChange(...) not kicking off when the isPresented value changes.

If that so, I'm doubting that this might be a SwiftUI bug. However, it's irritating that the bug happens on your device/simulator but not mine. Does your view belong to a bigger view hierarchy, or it just a simple VStack like you gave in the first example?

Thank you so much for taking your time, I really appreciate it. Hopefully we can get this fixed as quickly as possible.

LucasCarioca commented 4 years ago

Yeah the view itself is just a simple VStack it is being shown by a parent NavigationView (app drawer). To rule out any possible conflict there I also tried starting the app with that simple view as the main content view of the app and it still happens.

Just to clarify I have tried this in the following ways:

✅ = it worked ❌ = it didn't work

The only common thing I can tell is iOS13 vs iOS14.

LucasCarioca commented 4 years ago

Probably not as intended but I did get it to work using your hotfix branch. I pulled it down and made one small change and now it works. I think this kind of breaks other functionality you have though.

        if let dismissAfter = dismissAfter {
          DispatchQueue.main.asyncAfter(deadline: .now() + dismissAfter) {
            self.isPresented = false
            rootViewController?.dismiss(animated: true) // <-----ADDED THIS
          }
        }
LucasCarioca commented 4 years ago

Also without changing your code I got it working with the following:

.toast(isPresented: $showWarning) {
            ToastView{
                VStack {
                    ...
                    Button(action: {
                        self.showWarning = false
                        let keyWindow = UIApplication.shared.windows.first { $0.isKeyWindow }
                        let rootViewController = keyWindow?.rootViewController
                        rootViewController?.dismiss(animated: true)
                    }) {
                        Text("OK")
                    }
                }
            }
        }
quanshousio commented 4 years ago

Unfortunately, I still couldn't reproduce the bug. I currently don't have macOS 11.0 installed but I've tried all the following environments on latest macOS 10.15.6:

LucasCarioca commented 4 years ago

IOS13.6.1. Its very strange that its only happening that device. I’ll get ahold of another device to test on and make sure its not something strange with that.

If you want go ahead and lets consider that hotfix a resolution to this issue since it gives me at least a workaround. As long as it gets merged. I’ll look a little further into it on my end and if I can come up with a better way recreating it I’ll open a new issues with more information.

Thanks for taking the time to look into this!

quanshousio commented 4 years ago

No problem. Sorry for a little slow response as I'm still trying to figure out all the possible ways to reproduce this. By the way, have you tried cleaning the build folder, build from scratch and reinstall the application?

LucasCarioca commented 4 years ago

I’ll give that a shot but no need to keep this open. I would just close this when that hotfix is merged.

Also no worries on response, you have actually been responding very quickly! 👍

quanshousio commented 4 years ago

I just merged hotfix-1.0.2 into master. Keep me update as I want this issue to be completely resolved.

LucasCarioca commented 4 years ago

Closed in https://github.com/quanshousio/ToastUI/commit/e0d402f908ebf836e8750743b8d9eca085c03ea1

LucasCarioca commented 4 years ago

@quanshousio fyi, I have not found out what the problem was yet but it has nothing to do with your code. I must be doing something strange with my views and not realizing it. I used the toast in a different location and it works fine out of the box. Just thought you should know.

Also I love this project thanks for putting it together.

quanshousio commented 4 years ago

I'm glad it's working now, though I still feel weird that SwiftUI fails to call .onPreferenceChange() in some circumstances. Thank you so much for your support.