简体   繁体   English

SwiftUI:如何确定视图是在 NavigationView、Sheet 中呈现还是在根目录中?

[英]SwiftUI: How can I determine if a view is presented in a NavigationView, Sheet, or is the root?

I'm working on a project that requires a custom navigation bar that will have custom buttons and title styling, while also allowing an accessory view below the main nav portion.我正在开发一个需要自定义导航栏的项目,该导航栏将具有自定义按钮和标题样式,同时还允许主导航部分下方的辅助视图。

Essentially, I'd like to abstract away the need to choose the custom back button based on the presentation style.本质上,我想抽象出根据演示风格选择自定义后退按钮的需要。 If it's presented in a sheet, I plan to show an X icon.如果它显示在工作表中,我计划显示一个 X 图标。 If it is pushed onto a navigation view I want to show a back error.如果它被推到导航视图上,我想显示一个返回错误。 If it's a root view I want to hide the button altogether.如果它是根视图,我想完全隐藏该按钮。

I've mapped the presentationMode environment variable however when I access the isPresented value I always get true, even on the root view of my app.我已经映射了presentationMode环境变量,但是当我访问isPresented值时,我总是得到 true,即使在我的应用程序的根视图中也是如此。

Here's a general idea of what I'm working on:以下是我正在做的工作的大致思路:

import SwiftUI

struct CustomNavigationBar<Content>: View where Content: View {

    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    private let title: LocalizedStringKey
    private let content: (() -> Content)?

    private var backButton: AnyView? {

        let button = Button(action: { self.presentationMode.wrappedValue.dismiss() }) {
            // custom image extension, just resolves to a back icon
            Image.Icons.arrowBack
        }

        if (presentationMode.wrappedValue.isPresented) {
            return AnyView(button)
        } else {
            return nil
        }
    }

    public init(_ title: LocalizedStringKey, content: (() -> Content)? = nil) {
        self.title = title
        self.content = content
    }

    var body: some View {
        VStack {
            content?()
            Divider().foregroundColor(.gray)
        }.navigationBarTitle(title, displayMode: .large)
        .frame(minHeight: 96)
        .navigationBarBackButtonHidden(true)
        .navigationBarItems(leading: backButton)
    }
}

Does anyone have any experience or tips for accessing a view's place in the presentation hierarchy with SwiftUI?有没有人有任何使用 SwiftUI 访问视图在表示层次结构中的位置的经验或提示? Thanks!谢谢!

You can use SwiftUI-Introspect , used to "Introspect underlying UIKit components from SwiftUI".您可以使用SwiftUI-Introspect ,用于“从 SwiftUI 内省底层 UIKit 组件”。

Here is a working example of what you are looking for.这是您正在寻找的工作示例。 It is an interactive example, so you can click through the different modes.这是一个交互式示例,因此您可以单击不同的模式。

import Introspect
import SwiftUI

/* ... */

struct ContentView: View {
    
    @State private var testing = 1
    private let thingsToTest = 3
    
    var body: some View {
        VStack {
            Picker("Testing", selection: $testing) {
                ForEach(1 ... thingsToTest, id: \.self) { index in
                    Text("\(index)")
                        .tag(index)
                }
            }
            .pickerStyle(SegmentedPickerStyle())
            
            Divider()
            
            Spacer()
            
            switch testing {
            case 1:
                PresentationReader { kind in
                    Text("Hello! Kind: \(kind.rawValue)")
                }
                
            case 2:
                NavigationView {
                    PresentationReader { kind in
                        Text("Hello! Kind: \(kind.rawValue)")
                    }
                }
                
            case 3:
                Text("Parent")
                    .sheet(isPresented: .constant(true)) {
                        PresentationReader { kind in
                            Text("Hello! Kind: \(kind.rawValue)")
                        }
                    }
                
            default:
                fatalError("Unavailable")
            }
            
            Spacer()
        }
    }
}
enum Kind: String {
    case navigationView
    case root
    case sheet
}


struct PresentationReader<Content: View>: View {
    typealias PresentedContent = (Kind) -> Content
    
    @State private var kind: Kind = .root
    private let content: PresentedContent
    
    init(@ViewBuilder content: @escaping PresentedContent) {
        self.content = content
    }
    
    var body: some View {
        content(kind)
            .presentationReader(kind: $kind)
    }
}


extension View {
    func presentationReader(kind: Binding<Kind>) -> some View {
        self
            .introspectViewController { vc in
                let rootVC = UIApplication.shared.windows.first?.rootViewController
                let isRoot = vc === rootVC
                var isHosted: Bool { Introspect.findHostingView(from: vc.view) != nil }
                
                if isRoot {
                    kind.wrappedValue = .root
                } else if isHosted {
                    kind.wrappedValue = .navigationView
                } else {
                    kind.wrappedValue = .sheet
                }
            }
    }
}

It works by getting the current view controller the view is in.它通过获取视图所在的当前视图 controller 来工作。

  • If the class reference of the root view controller is the same as the current root view controller, this is the root view (meaning it isn't embedded in a NavigationView or .sheet(...) ).如果根视图 controller 的 class 引用与当前根视图 controller 相同,则这是根视图(意味着它没有嵌入到NavigationView.sheet(...) )。
  • If this is not the root, we then check if this view is embedded in a hosting view.如果这不是根,我们然后检查这个视图是否嵌入在一个宿主视图中。 If it is, it is in a NavigationView .如果是,它在NavigationView中。
  • If the view is neither the root or in a NavigationView , it is therefore in a .sheet(...) .如果视图既不是根也不是在NavigationView中,因此它在.sheet(...)中。

This is now what your CustomNavigationBar will look like with these 3 changes:现在,这就是您的CustomNavigationBar在以下 3 项更改后的样子:

struct CustomNavigationBar<Content>: View where Content: View {

    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    @State private var kind: Kind = .root  // <--- CHANGE #1
    
    private let title: LocalizedStringKey
    private let content: (() -> Content)?

    private var backButton: AnyView? {

        let button = Button(action: { self.presentationMode.wrappedValue.dismiss() }) {
            // custom image extension, just resolves to a back icon
            Image.Icons.arrowBack
        }

        if kind == .navigationView {  // <--- CHANGE #2
            return AnyView(button)
        } else {
            return nil
        }
    }

    public init(_ title: LocalizedStringKey, content: (() -> Content)? = nil) {
        self.title = title
        self.content = content
    }

    var body: some View {
        VStack {
            content?()
                .presentationReader(kind: $kind)  // <--- CHANGE #3
            
            Divider().foregroundColor(.gray)
        }.navigationBarTitle(title, displayMode: .large)
        .frame(minHeight: 96)
        .navigationBarBackButtonHidden(true)
        .navigationBarItems(leading: backButton)
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何有条件地向将在 NavigationView 中显示的 SwiftUI View 添加尾随的 navigationbaritem? - How to add a trailing navigationbaritem conditionally to a SwiftUI View which will be presented in a NavigationView? 如何检测返回到 SwiftUI 中 NavigationView 的根视图? - How to detect return to the root view of the NavigationView in SwiftUI? 如何在 SwiftUI 中显示为 sheet/fullscreenCover 的嵌套视图中禁用可刷新? - How to disable refreshable in nested view which is presented as sheet/fullscreenCover in SwiftUI? SwiftUIpresentationMode:检查视图是否按工作表呈现 - SwiftUI presentationMode: Check if view is presented by sheet 如果出现警报/工作表,则制作 SwiftUI 视图灰度 - Make SwiftUI view grayscale if an alert/sheet is presented 从 SwiftUI 中的其他/根视图中关闭呈现的工作表 - Dismiss presented sheet from other/root views in SwiftUI 如何更改 SwiftUI 应用程序(无 SceneDelegate 或 AppDelegate)中的根视图? - How can I change the root view in a SwiftUI app (no SceneDelegate or AppDelegate)? 如何使用 SwiftUI 将 CoreData 上下文传递给在 Swift 5 中作为工作表呈现的新视图? - How to pass CoreData Context to new View presented as a Sheet in Swift 5 using SwiftUI? SwiftUI NavigationView 详细视图绑定到工作表模式不起作用 - SwiftUI NavigationView detail view binding to sheet modal not working 如何从父视图中关闭 SwiftUI NavigationView - How to dismiss a SwiftUI NavigationView from a parent view
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM