kye
kye

Reputation: 2246

SwiftUI : Dismiss modal from child view

I'm attempting to dismiss a modal after its intended action is completed, but I have no idea how this can be currently done in SwiftUI. This modal is triggered by a @State value change. Would it be possible to change this value by observing a notification of sorts?

Desired actions: Root -> Initial Modal -> Presents Children -> Dismiss modal from any child

Below is what I've tried

Error: Escaping closure captures mutating 'self' parameter

struct AContentView: View {
    @State var pageSaveInProgress: Bool = false

    init(pages: [Page] = []) { 
    // Observe change to notify of completed action
        NotificationCenter.default.publisher(for: .didCompletePageSave).sink { (pageSaveInProgress) in
            self.pageSaveInProgress = false
        }
    }

    var body: some View {
        VStack {
        //ETC
            .sheet(isPresented: $pageSaveInProgress) {
                    ModalWithChildren()
            }
        }
    }
}

ModalWithChildren test action


Button(action: {
    NotificationCenter.default.post(
        name: .didCompletePageSave, object: nil)}, 
        label: { Text("Close") })

Upvotes: 3

Views: 1396

Answers (1)

Fabian
Fabian

Reputation: 5348

You can receive messages through .onReceive(_:perform) which can be called on any view. It registers a sink and saves the cancellable inside the view which makes the subscriber live as long as the view itself does.

Through it you can initiate @State attribute changes since it starts from the view body. Otherwise you would have to use an ObservableObject to which change can be initiated from anywhere.

An example:

struct MyView : View {
    @State private var currentStatusValue = "ok"
    var body: some View {
        Text("Current status: \(currentStatusValue)")
    }
    .onReceive(MyPublisher.currentStatusPublisher) { newStatus in
        self.currentStatusValue = newStatus
    }
}

A complete example

import SwiftUI
import Combine

extension Notification.Name {
    static var didCompletePageSave: Notification.Name {
        return Notification.Name("did complete page save")
    }
}

struct OnReceiveView: View {
    @State var pageSaveInProgress: Bool = true

    var body: some View {
        VStack {
            Text("Usual")
                .onReceive(NotificationCenter.default.publisher(for: .didCompletePageSave)) {_ in
                    self.pageSaveInProgress = false
            }
            .sheet(isPresented: $pageSaveInProgress) {
                ModalWithChildren()
            }
        }
    }
}

struct ModalWithChildren: View {
    @State var presentChildModals: Bool = false

    var body: some View {
        Button(action: {
            NotificationCenter.default.post(
                name: .didCompletePageSave,
                object: nil
            )
        }) { Text("Send message") }
    }
}

Upvotes: 6

Related Questions