Home > Mobile >  How to show popup from any view in SwiftUI
How to show popup from any view in SwiftUI

Time:10-27

I'm using swiftui and I'm working on big app. I have too many views, but I want to show a view that can pop up from any views within the app. It's almost impossible for me to put ".sheet" or "@State isPopUpShowing" on every view.swift.

Is there any way to code my popup once/in one file without messing with every single view file?

CodePudding user response:

You could inject an ObservableObject that manages the sheets in the root of your view hierarchy (ContentView in my example) and propagate it through using .environmentObject. Then, child views will be able to access the sheet manager and put up a sheet from anywhere.


class SheetManager : ObservableObject {
    struct SheetItem : Identifiable {
        var id = UUID()
        var message : String
    }
    
    @Published var sheetItem : SheetItem?
    
    @ViewBuilder func sheetForItem(item: SheetItem) -> some View {
        Text(item.message)
    }
    
    func buildAndShowSheet(message : String) {
        sheetItem = SheetItem(message: message)
    }
}

struct ContentView: View {
    @StateObject private var sheetManager = SheetManager()
    
    var body: some View {
        VStack {
            Text("Hello, world!")
            ChildView()
        }
        .sheet(item: $sheetManager.sheetItem) { item in
            sheetManager.sheetForItem(item: item)
        }
        .environmentObject(sheetManager)
    }
}

struct ChildView: View {
    @EnvironmentObject private var sheetManager : SheetManager
    
    var body: some View {
        VStack {
            Text("Child view that triggers sheet")
            Button("Open sheet") {
                sheetManager.buildAndShowSheet(message: "Sheet is open")
            }
        }
    }
}

To get slightly more advanced, because the child view doesn't need to respond to updates from the sheet manager, you could actually make it just a custom environment key:

private struct SheetManagerKey: EnvironmentKey {
    static let defaultValue = SheetManager()
}

extension EnvironmentValues {
    var sheetManager: SheetManager {
        get { self[SheetManagerKey.self] }
        set { self[SheetManagerKey.self] = newValue }
    }
}

class SheetManager : ObservableObject {
    struct SheetItem : Identifiable {
        var id = UUID()
        var message : String
    }
    
    @Published var sheetItem : SheetItem?
    
    @ViewBuilder func sheetForItem(item: SheetItem) -> some View {
        Text(item.message)
    }
    
    func buildAndShowSheet(message : String) {
        sheetItem = SheetItem(message: message)
    }
}

struct ContentView: View {
    @StateObject private var sheetManager = SheetManager()
    
    var body: some View {
        VStack {
            Text("Hello, world!")
            ChildView()
        }
        .sheet(item: $sheetManager.sheetItem) { item in
            sheetManager.sheetForItem(item: item)
        }
        .environment(\.sheetManager, sheetManager)
    }
}

struct ChildView: View {
    @Environment(\.sheetManager) private var sheetManager
    
    var body: some View {
        VStack {
            Text("Child view that triggers sheet")
            Button("Open sheet") {
                sheetManager.buildAndShowSheet(message: "Sheet is open")
            }
        }
    }
}
  • Related