Reputation: 2344
I have an app with many nested views, some which show a sheet based on a user action.
But I also have a sheet that I'd like to present on the main view based on a timer (ie, not a user action). But you can't have 2 sheets at the same time, so I'd like to check "something" to see if a sheet is already up, and not present the one from the timer.
I'd like to do this in a general way, and not check every place in the code where a sheet might be presented.
Any suggestions?
Upvotes: 1
Views: 4049
Reputation: 3498
In official document there is an environment isPresented, however it doesn't work.
@Environment(\.isPresented) private var isPresented
Upvotes: 1
Reputation: 2344
Ideally there'd be something in the core framework that could be queried to answer the question "Is there a sheet being shown?", but as a commenter pointed out, that is fraught with peril.
So I just decided to leave it alone, that the "default" behavior is fine (ie, it'll defer presenting the sheet until any other sheet is dismissed). In my case this is preferred to any other gyrations.
EDIT:
Eek! I just found out that if the sheet from the timer is popped up while an Alert is showing...it ruins the app. Once you dismiss the alert, any attempt to bring up any sheet anywhere fails. It's as if things got out of sync somewhere. I believe this is similar to:
Lingering popover causes a problem with alerts
If you have alerts in your app, you don't really want to do this.
Upvotes: 2
Reputation: 4006
Here is how you can handle the sheets - the example below is fully functioning, just pass the view model to the environment before calling TabsView()
in the App
.
Identifiable
object that will handle all the sheets in the program:// This struct can manage all sheets
struct CustomSheet: Identifiable {
let id = UUID()
let screen: TypeOfSheet
// All sheets should fit here
@ViewBuilder
var content: some View {
switch screen {
case .type1:
SheetType1()
case .type2(let text):
SheetType2(text: text)
default:
EmptyView()
}
}
// All types of sheets should fit here
enum TypeOfSheet {
case type1
case type2(text: String)
case none
}
}
@Published
var and one function in the view model; the var will tell the program what sheet is open:// Code to be included in the view model, so it can
// handle AND track all the sheets
class MyViewModel: ObservableObject {
// This is THE variable that will tell the code whether a sheet is open
// (and also which one, if necessary)
@Published var sheetView: CustomSheet?
func showSheet(_ sheet: CustomSheet.TypeOfSheet) {
// Dismiss any sheet that is already open
sheetView = nil
switch sheet {
case .none:
break
default:
sheetView = CustomSheet(screen: sheet)
}
}
}
viewModel.showSheet(...)
.sheet(item:)
to observe the type of sheet to openviewModel.sheet.screen
to know what sheet is openviewModel.showSheet(.none)
// Example: how to use the view model to present and track sheets
struct TabsView: View {
@EnvironmentObject var viewModel: MyViewModel
var body: some View {
TabView {
VStack {
Text("First tab. Sheet is \(String(describing: viewModel.sheetView?.screen ?? .none))")
.padding()
Button("Open sheet type 1") {
// Show a sheet of the first type
viewModel.showSheet(.type1)
}
}
.tabItem {Label("Tab 1", systemImage: "house")}
VStack {
Text("Second tab. Sheet is \(viewModel.sheetView == nil ? "Hidden" : "Shown")")
.padding()
Button("Open sheet type 2") {
// Show a sheet of the second type
viewModel.showSheet(.type2(text: "parameter"))
}
}
.tabItem {Label("Tab 2", systemImage: "plus")}
}
// Open a sheet - the one selected in the view model
.sheet(item: $viewModel.sheetView) { sheet in
sheet.content
.environmentObject(viewModel)
}
}
}
The following code completes the minimal reproducible example:
// Just some sample views for the sheets
struct SheetType1: View {
@EnvironmentObject var viewModel: MyViewModel
var body: some View {
Text("Takes no parameters. Sheet is \(viewModel.sheetView == nil ? "Hidden" : "Shown")")
}
}
struct SheetType2: View {
@EnvironmentObject var viewModel: MyViewModel
let text: String
var body: some View {
Text("Takes a string: \(text). Sheet is \(String(describing: viewModel.sheetView?.screen ?? .none))")
}
}
@main
struct MyApp: App {
let viewModel = MyViewModel()
var body: some Scene {
WindowGroup {
TabsView()
.environmentObject(viewModel)
}
}
}
Upvotes: 1