简体   繁体   中英

Determine if a view is presented inside a popover

I have a view which presents in a popover if the size class is regular, and as a sheet, if size class is compact (standard system presentation behavior). In this view, I have a navigation view. I'd like to only display a “Done” toolbar item if the view is presented as a sheet, but not if presented in a popover.

In UIKit, this is very easy to accomplish, but in SwiftUI, I don't see any environment value that describes the presentation of the view.

I saw suggestions to look at the horizontalSizeClass environment value, but that doesn't work, because the view is presented with a compact size class inside the popover.

Here is my view:

struct SettingsView: View {
    fileprivate let internalSettings = SettingsViewInternal()
    @Environment(\.dismiss) var dismiss
    @Environment(\.horizontalSizeClass) var sizeClass
    
    var body: some View {
        NavigationStack {
            internalSettings
                .navigationTitle("Settings")
                .toolbar {
                    ToolbarItem(placement: .navigationBarLeading) {
                        Button("Reset") {
                            internalSettings.reset()
                        }
                    }
                    //This gets called twice, first time with a standard size class,
                    //then with compact size class when the popover presents its content.
                    if sizeClass == .compact {
                        ToolbarItem(placement: .confirmationAction) {
                            Button("Done") {
                                self.dismiss()
                            }
                        }
                    }
                }
        }
    }
}

I present the popover like this:

Button {
    settingsPresented = true
} label: {
    Image("gears")
}
.popover(isPresented: $settingsPresented) {
    SettingsView()
}

In such scenario a possible solution is to track global horizontalSizeClass instead of local (which by Apple's doc depends on many things), and inject it into some (say AppState ) environment object that can be transferred into any presentation.

So it could look like

struct ContentView: View {
    @StateObject private var appState = AppState()
    @Environment(\.horizontalSizeClass) var sizeClass

    var body: some View {
       SomeTopView()
         .onAppear {
            appState.sizeClass = sizeClass
         }
         .onChange(of: sizeClass) {
            appState.sizeClass = $0
         }
         .environmentObject(appState)  // << for all view hierarchy !!
    }
}

// ...

    Button {
        settingsPresented = true
    } label: {
        Image("gears")
    }
    .popover(isPresented: $settingsPresented) {
        SettingsView()
          .environmentObject(appState) // << should be injected explicitly !!
    }

// ...

struct SettingsView: View {
    @EnvironmentObject var appState: AppState

// ...

    if appState.sizeClass == .compact {
        ToolbarItem(placement: .confirmationAction) {

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM