Alvar
Alvar

Reputation: 539

Why is it in SwiftUI, when presenting modal view with .sheet, init() is called twice

I am producing the situation on WatchOS with the following code

struct Modal : View {
    @Binding var showingModal : Bool
    
    init(showingModal : Binding<Bool>){
        self._showingModal = showingModal
        print("init modal")
    }
    
    var body: some View {
        Button(action: {
            self.showingModal.toggle()
        }, label: {
            Text("TTTT")
        })
    }
}

struct ContentView: View {
    @State var showingModal = false
    var body: some View {
        Button(action: {
            self.showingModal.toggle()
        }, label: {
            Text("AAAA")
        }).sheet(isPresented: $showingModal, content: {Modal(showingModal: self.$showingModal)})
    }
}

Every time I press the button in the master view to summon the modal with .sheet, Two instances of the modal view are created.

Could someone explain this phenomenon?

Upvotes: 18

Views: 4531

Answers (5)

Nickolay Tarbayev
Nickolay Tarbayev

Reputation: 406

Seems to be a bug in the sheet and fullScreenCover modifiers. In my case it was calling its content closure also after showing, hiding the keyboard and typing. To prevent the view from being recreated we can wrap it in a view adopting Equatable which is always equal to its instances:

struct PersistentContentView<Content: View>: View, Equatable {
    static func == (lhs: PersistentContentView<Content>, rhs: PersistentContentView<Content>) -> Bool {
        true
    }

    let content: () -> Content

    var body: some View {
        content()
    }
}

extension View {
    func persistent() -> some View {
        PersistentContentView { self }
    }
}
    .sheet(isPresented: $showingModal) {
        Modal(showingModal: self.$showingModal)
            .persistent()
    }

Upvotes: 0

ceojosef
ceojosef

Reputation: 21

private let uniqueId: String = "uniqueId" 

Button(action: {
    self.showingModal.toggle()
}, label: {
    Text("AAAA")
})
    .sheet(isPresented: $showingModal) {
        Modal(showingModal: self.$showingModal)
          .id("some-unique-id")
    }

Ex:

.id(self.uniqueId)

Add unique id to your .sheet and not't worry :)

But, do not use UUID(), because sheet view will be represented on every touch event

Upvotes: 1

Jeremy
Jeremy

Reputation: 3099

I tracked this down in my code to having the following line in my View:

@Environment(\.presentationMode) var presentation

I had been doing it due to https://stackoverflow.com/a/61311279/155186, but for some reason that problem seems to have disappeared for me so I guess I no longer need it.

I've filed Feedback FB7723767 with Apple about this.

Upvotes: 9

Adrian
Adrian

Reputation: 555

It is probably a bug, as of Xcode 11.4.1 (11E503a). Beware, that if for example initializing view models (or anything else for that matter) like so:

.sheet(isPresented: $isEditingModalPresented) {
    LEditView(vm: LEditViewModel(), isPresented: self.$isEditingModalPresented)
}

the VM will be initialized twice.

Upvotes: 2

Gayal Rupasinghe
Gayal Rupasinghe

Reputation: 144

Comment out/remove the init() method from Modal with everything else the same. You should be able to solve the issue of two instances of Modal being created, its because you are explicitly initializing the binding (showingModal) in the init() of Modal. Hope this makes sense.

Upvotes: 0

Related Questions