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")
}
}
}
}