SwiftUI hide navigation bar of UIKit UINavigationController(rootViewController: _)
The UINavigationController
should be in view hierarchy so that could have effect.
Here is worked variant, assuming you inject it into window in SceneDelegate
(tested with Xcode 11.4 / iOS 13.4)
extension UIViewController{
func inNavigation() -> UIViewController {
let navigationController = UINavigationController(rootViewController: self)
DispatchQueue.main.async {
navigationController.isNavigationBarHidden = true
}
return navigationController
}
}
How to hide a navigation bar from first ViewController in Swift?
If you know that all other views should have the bar visible, you could use viewWillDisappear
to set it to visible again.
In Swift:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.setNavigationBarHidden(false, animated: animated)
}
How to remove the default Navigation Bar space in SwiftUI NavigationView
For some reason, SwiftUI requires that you also set .navigationBarTitle
for .navigationBarHidden
to work properly.
NavigationView {
FileBrowserView(jsonFromCall: URLRetrieve(URLtoFetch: applicationDelegate.apiURL))
.navigationBarTitle("")
.navigationBarHidden(true)
}
Update
As @Peacemoon pointed out in the comments, the navigation bar remains hidden as you navigate deeper in the navigation stack, regardless of whether or not you set navigationBarHidden
to false
in subsequent views. As I said in the comments, this is either a result of poor implementation on Apple's part or just dreadful documentation (who knows, maybe there is a "correct" way to accomplish this).
Whatever the case, I came up with a workaround that seems to produce the original poster's desired results. I'm hesitant to recommend it because it seems unnecessarily hacky, but without any straightforward way of hiding and unhiding the navigation bar, this is the best I could do.
This example uses three views - View1
has a hidden navigation bar, and View2
and View3
both have visible navigation bars with titles.
struct View1: View {
@State var isNavigationBarHidden: Bool = true
var body: some View {
NavigationView {
ZStack {
Color.red
NavigationLink("View 2", destination: View2(isNavigationBarHidden: self.$isNavigationBarHidden))
}
.navigationBarTitle("Hidden Title")
.navigationBarHidden(self.isNavigationBarHidden)
.onAppear {
self.isNavigationBarHidden = true
}
}
}
}
struct View2: View {
@Binding var isNavigationBarHidden: Bool
var body: some View {
ZStack {
Color.green
NavigationLink("View 3", destination: View3())
}
.navigationBarTitle("Visible Title 1")
.onAppear {
self.isNavigationBarHidden = false
}
}
}
struct View3: View {
var body: some View {
Color.blue
.navigationBarTitle("Visible Title 2")
}
}
Setting navigationBarHidden
to false
on views deeper in the navigation stack doesn't seem to properly override the preference of the view that originally set navigationBarHidden
to true
, so the only workaround I could come up with was using a binding to change the preference of the original view when a new view is pushed onto the navigation stack.
Like I said, this is a hacky solution, but without an official solution from Apple, this is the best that I've been able to come up with.
Navigation Bar Items not shown when UIKit SwiftUI UIKit
SwiftUI view used its own navigation controller and it ignores the UIKit navigation controller so the possible solution is to set your navigation bar to parent controller and set your navigation bar element by coding.
like this
class MyUIKitViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// ADD NAV BUTTON HERE - must be in DispatchQueue
DispatchQueue.main.async {
self.parent?.navigationItem.title = "Your Title"
self.parent?.navigationItem.rightBarButtonItems?.append(UIBarButtonItem(systemItem: .camera))
}
}
}
Another solution is to use self.navigationController?.navigationBar.topItem?
. This is the exact same as above. It gives a topItem.
class MyUIKitViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// ADD NAV BUTTON HERE - must be in DispatchQueue
DispatchQueue.main.async {
self.navigationController?.navigationBar.topItem?.title = "Your Title"
self.navigationController?.navigationBar.topItem?.rightBarButtonItems?.append(UIBarButtonItem(systemItem: .camera))
}
}
}
I used DispatchQueue for when the view is initiated then the parent is not set at this time. (You can use also use viewWillAppear
instead of DispatchQueue.main.async
)
Edit
If you don't want to add elements programmatically and if you need to use the same element from the storyboard then you can use this approch.
class MyUIKitViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// ADD NAV BUTTON HERE
navigationItem.rightBarButtonItems?.append(UIBarButtonItem(systemItem: .camera))
DispatchQueue.main.async { //<--Here
self.navigationController?.navigationBar.topItem?.title = self.navigationItem.title
self.navigationController?.navigationBar.topItem?.rightBarButtonItems = self.navigationItem.rightBarButtonItems
}
}
}
How to dynamically hide the status bar and the home indicator in SwiftUI?
I was able to solve the problem with the SwiftUI view not extending beyond the safe area insets for the status bar and the home indicator by completely switching to a storyboard based project template and embedding my views through a custom UIHostingController as described in this solution by Casper Zandbergen.
Before I was re-integrating the hosting controller into the SwiftUI view hierarchy by wrapping it with a UIViewRepresentable instance, which must have caused the complications in handling the safe area.
By managing the whole app through the custom UIHostingController subclass it was even easier to get the hiding of the home indicator working. As much as I love SwiftUI I had to realize that, with its current limitations, UIKit was the better option here.
Final code (optimized version of the solution linked above):
ViewController.swift
import SwiftUI
import UIKit
struct HideUIPreferenceKey: PreferenceKey {
static var defaultValue: Bool = false
static func reduce(value: inout Bool, nextValue: () -> Bool) {
value = nextValue() || value
}
}
extension View {
func userInterfaceHidden(_ value: Bool) -> some View {
preference(key: HideUIPreferenceKey.self, value: value)
}
}
class ViewController: UIHostingController<AnyView> {
init() {
weak var vc: ViewController? = nil
super.init(
rootView: AnyView(
ContentView()
.onPreferenceChange(HideUIPreferenceKey.self) {
vc?.userInterfaceHidden = $0
}
)
)
vc = self
}
@objc required dynamic init?(coder: NSCoder) {
weak var vc: ViewController? = nil
super.init(
coder: coder,
rootView: AnyView(
ContentView()
.onPreferenceChange(HideUIPreferenceKey.self) {
vc?.userInterfaceHidden = $0
}
)
)
vc = self
}
private var userInterfaceHidden = false {
didSet { setNeedsUpdateOfHomeIndicatorAutoHidden() }
}
override var prefersStatusBarHidden: Bool {
userInterfaceHidden
}
override var prefersHomeIndicatorAutoHidden: Bool {
userInterfaceHidden
}
}
Related Topics
How to Reload a UI View's Content Swift
Occlusion Material or Hold-Out Shader in Arkit and Scenekit
How to Link Xctest Dependency to Production/Main Target
Spritekit Not Deallocating All Used Memory
Background Animation with Depth in Spritekit
Get Location from Center of Screen Swift Mapkit
With Firebase, Swift Removeobserver(Withhandle Does Not Remove the Observer
How to Code Initwithcoder in Swift
How to Add a Watermark to an Image Using This Code
Watchos3 Complication That Launches App
Convert Opengl Shader to Metal (Swift) to Be Used in Cifilter
How to Convert Delegate to Observable Rxswift
Is It Possible in Swift to Add Variables to an Object at Runtime
Sub-Classing Nstextstorage Causes Significant Memory Issues
How to Make a Https Request to a Server in Swift
Convert Timestamp String with Epochal Time and Timezone into Nsdate