我试图在UIKitUIViewController
中放置一些SwiftUI时隐藏navigationBar
:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: animated)
但它不会消失。然而,当我拿走SwiftUI时,它可以工作。有谁知道如何解决这个问题?
编辑:
我正在实例化这样的视图:
let controller = UIHostingController(rootView: view())
其中view
是 SwiftUI,然后将其添加到UIView()
中,就像添加任何 UIKit 元素一样。
UIHostingViewController 尊重 SwiftUI 视图的navigationBarHidden
值。您可以在 SwiftUI 视图结束时调用.navigationBarHidden(true)
,也可以使用以下示例中所示的自定义 UIHostingController 子类。
溶液:
import SwiftUI
import UIKit
class YourHostingController <Content>: UIHostingController<AnyView> where Content : View {
public init(shouldShowNavigationBar: Bool, rootView: Content) {
super.init(rootView: AnyView(rootView.navigationBarHidden(!shouldShowNavigationBar)))
}
@objc required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
用法示例:
let hostVc = YourHostingController(shouldShowNavigationBar: false, rootView: YourSwiftUIView())
使用修饰符.navigationBarHidden(true)
在我们的例子中不起作用。它没有效果。
我们的解决方案是子类化UIHostingController
,根本不让它访问UINavigationController
。例如:
import UIKit
import SwiftUI
final public class RestrictedUIHostingController<Content>: UIHostingController<Content> where Content: View {
/// The hosting controller may in some cases want to make the navigation bar be not hidden.
/// Restrict the access to the outside world, by setting the navigation controller to nil when internally accessed.
public override var navigationController: UINavigationController? {
nil
}
}
请注意,此解决方案依赖于 UIKit 和 SwiftUI 中的基础代码访问UINavigationController
并根据UIViewController.navigationController
-属性设置导航栏隐藏状态。如果苹果决定改变这一假设,这可能会在未来中断。
昨天也遇到了这个问题。
我正在展示一个模态UINavigationController
,UIViewController
作为rootViewController
,它通过UIHostingController
嵌入了SwiftUI视图。
在viewDidAppear
UIViewController
设置常用setNavigationBarHidden
会在嵌入 SwiftUI 视图后立即停止工作。
概述:
Root ViewController: setNavigationBarHidden in viewWillAppear
Navigation Bar Visible:
UINavigationController > root UIViewController > embedded UIHostingController
Navigation Bar Invisible:
UINavigationController > root UIViewController > no UIHostingController
经过一些调试后,我意识到UIHostingController
本身再次调用setNavigationBarHidden
。
所以这个问题的原因是,UIHostingControllers
改变了周围的UINavigationController
UINavigationBar
。
一个简单的解决方法:
在UIHostingController
嵌入的第一个显示的 SwiftUI 视图中设置导航栏属性。
var body: some View {
MyOtherView(viewModel: self.viewModel)
.navigationBarHidden(true)
}
这将还原 SwiftUI 和UIHostingController
尝试应用于周围UINavigationController
的调整。
由于无法保证 SwiftUI 和 UIKit 之间的交互(它使用底层 UIKit),我建议将周围的setNavigationBarHidden
与这个修饰符一起保留在周围的viewDidAppear
中。
就我而言,我必须使用这个UIHostingController子类。
class NavigationBarHiddenUIHostingController<Content: View>: UIHostingController<Content> {
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if navigationController?.isNavigationBarHidden == false {
navigationController?.isNavigationBarHidden = true
}
}
}
当在viewDidAppear而不是viewWillAppear中调用setNavigationBarHidden 时,从扩展UIHostingController的类中隐藏导航栏似乎有效。
override func viewDidAppear(_ animated: Bool) {
navigationController?.setNavigationBarHidden(true, animated: false)
super.viewDidAppear(animated)
}
我想在这里包含我的方法,以防有人在使用 SwiftUI 时发现它很有用。 我发现问题是UIHostingController覆盖了我声明中的某些内容
navigationController?.setNavigationBarHidden(true, animated: false)
所以我只是创建了一个自定义的UIHostingController并使用了viewWillAppear(_ animated:Bool):
class UIHostingViewControllerCustom:UIHostingController<YourView>{
override func viewWillAppear(_ animated: Bool) {
navigationController?.setNavigationBarHidden(true, animated: false)
}
}
然后,当您将该UIHostingController添加到ViewController中时:
let hostingController = UIHostingViewControllerCustom(rootView: YourView())
hostingController.view.backgroundColor = .clear
addChild(hostingController)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(hostingController.view)
hostingMapView.didMove(toParent: self)
//Constraints
hostingController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
hostingController.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
hostingController.view.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: -view.safeAreaInsets.top).isActive = true
hostingController.view.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -view.safeAreaInsets.bottom).isActive = true
对我没有任何用处,所以我添加一个观察者来隐藏父视图中navigationBar
:
private var observer: NSKeyValueObservation?
override func viewDidLoad() {
super.viewDidLoad()
observer = navigationController?.observe(
.navigationBar.isHidden,
options: [.new]
) { [weak self] _, change in
guard change.newValue == false else { return }
self?.navigationController?.navigationBar.isHidden = true
}
}
这在 iOS 16 上似乎是固定的:如果你为-[UINavigationController setNavigationBarHidden:animated:]
添加一个符号断点,并且你p $arg3
你会发现它在 iOS 14/15 上是 nil(false),现在在 iOS 16 上是 1(true),以防您在此内部调用发生之前确实在某个地方调用了setNavigationBarHidden(true
,即内部调用不再覆盖您的代码。
在iOS 14上,@TParizek的解决方案有效(修饰符.navigationBarHidden(true)
),但在iOS 15上,我必须在第一个viewDidLayoutSubviews
呼叫时拨打setNavigationBarHidden(true
。
我使用 SwiftUI 自省库来隐藏仅针对低于 16 的操作系统版本显示的额外导航栏。
.introspectNavigationController(customize: { navigationController in
navigationController.navigationBar.isHidden = true
})
不幸的是,如果你在没有UINavigationController的情况下制作UIHostingViewController,你需要对框架本身进行一些调整(实际上是将其topAnchor减少到48)。 导航栏间距似乎只显示在下一个视图将出现和子视图的布局上。
这是我用于UIHostingViewController的解决方案。
首先,我做了一个函数(在我的UIHostingViewController内部),它将设置我的内部子视图的origin(x,y),并将约束设置为self.view。 它有条件(不要每次都这样做,只有在导航栏间距出现时):
private var savedView: UIView?
private func removeAdditionalTopSpacing() {
if view.subviews.count == 0 {
return
}
var widgetFrame = view.subviews[0].frame
let widgetStartingPoint = widgetFrame.origin.y
widgetFrame.origin.y = 0
widgetFrame.origin.x = 0
self.view.subviews[0].frame = widgetFrame
self.view.subviews[1].frame = widgetFrame
if widgetStartingPoint > 0 {
self.savedView = self.view
self.savedView?.translatesAutoresizingMaskIntoConstraints = false
self.savedView?.widthAnchor.constraint(equalTo: self.savedView!.subviews[0].widthAnchor).isActive = true
self.savedView?.heightAnchor.constraint(equalTo: self.savedView!.subviews[0].heightAnchor).isActive = true
self.savedView?.centerXAnchor.constraint(equalTo: self.savedView!.subviews[0].centerXAnchor).isActive = true
self.savedView?.centerYAnchor.constraint(equalTo: self.savedView!.subviews[0].centerYAnchor).isActive = true
self.view = self.savedView
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
}
重要提示: 我之所以将当前视图保存在私有变量 savedView 中,是因为他的存在和内存释放。这样,当removeFromSuperView被调用时,它就不会丢失。 UIHostingViewController.view总是有2个子视图。一个用于内容,另一个用于命中范围。当导航栏间距显示时,两者都向下移动 48 磅。
有两个地方我称之为:viewDidAppear()和viewDidLayoutSubviews():
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
removeAdditionalTopSpacing()
}
public override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
removeAdditionalTopSpacing()
}
大家好,这是我的解决方案 如何隐藏和返回导航栏
import Foundation
import SwiftUI
import UIKit
class HostingController <Content>: UIHostingController<AnyView> where Content : View {
private weak var previousViewController: UIViewController?
private var shouldShowNavigationBar: Bool
private let shouldShowNavigationBarAfterBack: Bool
public init( rootView: Content, previousViewController: UIViewController?,
shouldShowNavigationBar: Bool = false, shouldShowNavigationBarAfterBack: Bool = true) {
self.previousViewController = previousViewController
self.shouldShowNavigationBar = shouldShowNavigationBar
self.shouldShowNavigationBarAfterBack = shouldShowNavigationBarAfterBack
super.init(rootView: AnyView(rootView))
}
override func viewDidLayoutSubviews() {
navigationController?.setNavigationBarHidden(!shouldShowNavigationBar, animated: false)
super.viewDidLayoutSubviews()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
DispatchQueue.main.async { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf
.previousViewController?
.navigationController?
.setNavigationBarHidden(!strongSelf.shouldShowNavigationBarAfterBack, animated: false)
}
}
@objc required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
如何使用它:
let viewController = HostingController(rootView: view, previousViewController: previousViewController)
如果您需要所有参数,则不要默认,您可以调用:
let viewController = HostingController(rootView: view, previousViewController: previousViewController, shouldShowNavigationBar: false, shouldShowNavigationBarAfterBack: false)
previousViewController
- 它是一个控制器,可以推动这个新的控制器。
我认为有一个更简单的解决方案。 如果您使用的是 Xcode 15 测试版, 您可以重写 func viewIsAppearing(_ animated: Bool) 并实现 Func viewIsAppearing(_ Animated: bool) 来处理隐藏和出现在导航栏中的问题,并且它非常有效。
你知道你在 swiftUI 中把 UIKit 函数放在哪里吗?
内部
var body: some View {
}
你需要调用你的ViewControllerWrapper类,该类需要包含一些方法才能使用你的UIKit类。 UIViewController可表示的实现它也需要。