[英]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 来工作。
NavigationView
or .sheet(...)
).如果根视图 controller 的 class 引用与当前根视图 controller 相同,则这是根视图(意味着它没有嵌入到NavigationView
或.sheet(...)
)。NavigationView
.如果是,它在NavigationView
中。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.