diniska / modal-view

Present Modal view with Swift UI in the same way you would do with NavigationView
MIT License
92 stars 9 forks source link

Dismissed on EnvironmentObject change #8

Open Erhannis opened 4 years ago

Erhannis commented 4 years ago

This library is much more convenient than the default way of doing modals, BUT, we have a view that gets automatically updated once a second or so. (It's tied to an EnvironmentObject.) When this happens, the modal disappears. Ideally, I'd like it to stay visible, and preferably be updated according to the changes which have occurred in the EnvironmentVariable, while the view under the modal is updated in the background. Can this be done?

diniska commented 4 years ago

@Erhannis Thanks for your question. Could you please provide an example of your view structure? I'm pretty sure that this can be solved by moving ModalPresenter a few levels above your updated view. The code would be helpful to better understand the problem and give a better answer.

Erhannis commented 4 years ago

I may be able to make a demo view later, but the actual view in question is company code, so I unfortunately cannot provide the code. In any case, I believe that while testing, I'd placed the ModalPresenter at the top level, or nearly. It was above the modal ModalLink, at least.

I suspect the problem is related to the following in the source comments for "sheet(item:onDismiss:content:)" : "item: a Binding to an optional source of truth for the sheet. [...] If the identity changes, the system will dismiss a currently-presented sheet and replace it by a new sheet." Perhaps the change in an EnvironmentVariable triggers some change in the content, so the system dismisses the sheet, but for some reason doesn't recreate it? I'll try to make a toy example later if I have time.

Erhannis commented 4 years ago

Here's a minimal example demonstrating the problem:

import SwiftUI

struct ContentView: View {
    @EnvironmentObject var eo: EnvObj

    var body: some View {
        ModalPresenter {
            ModalLink(destination: Text("Modal")) {
                Text("Button")
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

final class EnvObj: ObservableObject {
    @Published var number: Int64 = 0

    init() {
        NSLog("init EnvObj")
        Thread {
            while true {
                Thread.sleep(forTimeInterval: 4.0)
                DispatchQueue.main.async {
                    self.number = self.number + 1
                    NSLog("inc number -> \(self.number)")
                }
            }
        }.start()
    }
}

In SceneDelegate.swift, also change

let contentView = ContentView()

to

let contentView = ContentView().environmentObject(EnvObj())

I think that's all that's required. When you tap Button, the modal should appear correctly, but disappears in 4 seconds or less, when the EnvironmentObject is updated.

The bug occurred on both a physical device and a simulator.

diniska commented 4 years ago

@Erhannis thanks for the example. I'm able to reproduce it and going to fix the issue. Until then, you can use the next refactoring as a temporary solution: Leave ModalPresenter inside the ContentView and move the rest into a temporary view.

struct ContentView: View {
    var body: some View {
        ModalPresenter {
            EnvironmentContentView()
        }
    }
}

private struct EnvironmentContentView: View {
    @EnvironmentObject var eo: EnvObj

    var body: some View {
        ModalLink(destination: Text("Modal")) {
            Text("Button")
        }
    }
}

This change leaves ContentView unaffected by changes and thus doesn't dismiss the modal. It also shouldn't have a measurable effect on performance as SwiftUI Views are very cheap.

Erhannis commented 4 years ago

Thanks! I'll try that next time it comes up.

On Mon, Jan 20, 2020 at 11:50 PM Denis notifications@github.com wrote:

@Erhannis https://github.com/Erhannis thanks for the example. I'm able to reproduce it and going to fix the issue. Until then, you can use the next refactoring as a temporary solution: Leave ModalPresenter inside the ContentView and move the rest into a temporary view.

struct ContentView: View { var body: some View { ModalPresenter { EnvironmentContentView() } } } private struct EnvironmentContentView: View { @EnvironmentObject var eo: EnvObj

var body: some View {
    ModalLink(destination: Text("Modal")) {
        Text("Button")
    }
}

}

This change leaves ContentView unaffected by changes and thus doesn't dismiss the modal. It also shouldn't have a measurable effect on performance as SwiftUI Views are very cheap.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/diniska/modal-view/issues/8?email_source=notifications&email_token=AAXCNTPNOV2BWJOYXLQZDCDQ6Z5IFA5CNFSM4KBTBA3KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEJOPFTQ#issuecomment-576516814, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAXCNTPJQAV4CRJHNE7EVDTQ6Z5IFANCNFSM4KBTBA3A .

bark1n9 commented 4 years ago

@diniska Were you able to fix this?

diniska commented 4 years ago

Hello, @bark1n9! My current suggestion is to separate the ModalPresenter and the view that uses EnvironmentObject so that the reload is no triggered. This is a pretty easy refactoring as shown here: https://github.com/diniska/modal-view/issues/8#issuecomment-576516814.

bark1n9 commented 4 years ago

@diniska Thank you! I somehow missed that comment :)