在 UIKit 中嵌入 SwiftUI 时无法隐藏导航栏



我试图在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-属性设置导航栏隐藏状态。如果苹果决定改变这一假设,这可能会在未来中断。

昨天也遇到了这个问题。

我正在展示一个模态UINavigationControllerUIViewController作为rootViewController,它通过UIHostingController嵌入了SwiftUI视图。

viewDidAppearUIViewController设置常用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改变了周围的UINavigationControllerUINavigationBar

一个简单的解决方法:

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可表示的实现它也需要。

相关内容

  • 没有找到相关文章

最新更新