netshark1000
netshark1000

Reputation: 7403

SwiftUI: Hide Statusbar on NavigationLink destination

I have a master detail structure with a list on master and a detail page where I want to present a webpage fullscreen, so no navigation bar and no status bar. The user can navigate back by a gesture (internal app).

I'm stuggeling hiding the statusbar with

.statusBar(hidden: true)

This works on master page, but not on detail page.

Hiding the navigation bar works fine with my ViewModifier

public struct NavigationAndStatusBarHider: ViewModifier {
    @State var isHidden: Bool = false

    public func body(content: Content) -> some View {
        content
            .navigationBarTitle("")
            .navigationBarHidden(isHidden)
            .statusBar(hidden: isHidden)
            .onAppear {self.isHidden = true}
    }
}

extension View {
    public func hideNavigationAndStatusBar() -> some View {
        modifier(NavigationAndStatusBarHider())
    }
}

Any idea?

Upvotes: 4

Views: 1708

Answers (4)

Kerem Cesme
Kerem Cesme

Reputation: 118

Solution for Xcode 12.5 and IOS 14.6:

Add the following to your Info.plist:

<key>UIStatusBarHidden</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>

Add UIApplication.shared.isStatusBarHidden = false the following to your AppDelegate.swift:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        UIApplication.shared.isStatusBarHidden = false // -> This

        return true
    }

You can then hide and bring back the status bar anywhere in the app with the UIApplication.shared.isStatusBarHidden modifier.

Example:

struct Example: View {
    
    // I'm checking the safe area below the viewport 
    // to be able to detect iPhone models without a notch.
    @State private var bottomSafeArea = UIApplication.shared.windows.first?.safeAreaInsets.bottom
    
    var body: some View {
        
          Button {
              if bottomSafeArea == 0 {
                  UIApplication.shared.isStatusBarHidden = true // or use .toggle()
              }
          } label: {
              Text("Click here for hide status bar!")
                  .font(.title2)
        }
    }
}

Upvotes: 1

ADB
ADB

Reputation: 719

I have a NavigationView that contains a view that presents a fullScreenModal. I wanted the status bar visible for the NavigationView, but hidden for the fullScreenModal. I tried multiple approaches but couldn't get anything working (it seems lots of people are finding bugs with the status bar and NavigationView on iOS14).

I've settled on a solution which is hacky but seems to work and should do the job until the bugs are fixed.

Add the following to your Info.plist:

<key>UIStatusBarHidden</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>

And then add the following in your view's init when necessary (changing true/false as required): UIApplication.shared.isStatusBarHidden = false

For example:

struct ContentView: View {

    init() {
        UIApplication.shared.isStatusBarHidden = true
    }

    var body: some View {
        Text("Hello, world!")
    }
}

It'll give you a deprecated warning, but I'm hoping this is a temporary fix.

Upvotes: 1

Felix Marianayagam
Felix Marianayagam

Reputation: 2714

I've been trying this for a couple of hours out of curiosity. At last, I've got it working.

The trick is to hide the status bar in the Main view, whenever the user navigates to the detail view. Here's the code tested in iPhone 11 Pro Max - 13.3 and Xcode version 11.3.1. Hope you like it ;). Happy coding.

Main View Detail View

import SwiftUI
import UIKit
import WebKit

struct ContentView: View {
    var urls: [String] = ["https://www.stackoverflow.com", "https://www.yahoo.com"]
    @State private var hideStatusBar = false

    var body: some View {
        NavigationView {
            List {
                ForEach(urls, id: \.self) { url in
                    VStack {
                        NavigationLink(destination: DetailView(url: url)) {
                            Text(url)
                        }
                        .onDisappear() {
                            self.hideStatusBar = true
                        }
                        .onAppear() {
                            self.hideStatusBar = false
                        }
                    }
                }
            }
            .navigationBarTitle("Main")
        }
        .statusBar(hidden: hideStatusBar)
    }
}

struct DetailView: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    var url: String = ""

    var body: some View {
        VStack {
            Webview(url: url)
            Button("Tap to go back.") {
                self.presentationMode.wrappedValue.dismiss()
            }
            Spacer()
        }
        .hideNavigationAndStatusBar()
    }
}

public struct NavigationAndStatusBarHider: ViewModifier {
    @State var isHidden: Bool = false

    public func body(content: Content) -> some View {
        content
            .navigationBarTitle("")
            .navigationBarHidden(isHidden)
            .statusBar(hidden: isHidden)
            .onAppear {self.isHidden = true}
    }
}

struct Webview: UIViewRepresentable {
    var url: String
    typealias UIViewType = WKWebView

    func makeUIView(context: UIViewRepresentableContext<Webview>) -> WKWebView {
        let wkWebView = WKWebView()
        guard let url = URL(string: self.url) else {
            return wkWebView
        }

        let request = URLRequest(url: url)
        wkWebView.load(request)
        return wkWebView
    }

    func updateUIView(_ uiView: WKWebView, context: UIViewRepresentableContext<Webview>) {
    }
}

extension View {
    public func hideNavigationAndStatusBar() -> some View {
        modifier(NavigationAndStatusBarHider())
    }
}

Upvotes: 6

Asperi
Asperi

Reputation: 257711

Well, your observed behaviour is because status bar hiding does not work being called from inside NavigationView, but works outside. Tested with Xcode 11.2 and(!) Xcode 11.4beta3.

Please see below my findings.

demo1 demo2

          Case1                         Case2

Case1: Inside any stack container

struct TestNavigationWithStatusBar: View {
    var body: some View {
        VStack {
            Text("Hello, World!")
                .statusBar(hidden: true)
        }
    }
}

Case2: Inside NavigationView

struct TestNavigationWithStatusBar: View {
    var body: some View {
        NavigationView {
            Text("Hello, World!")
                .statusBar(hidden: true)
        }
    }
}

The solution (fix/workaround) to use .statusBar(hidden:) outside of navigation view. Thus you should update your modifier correspondingly (or rethink design to separate it).

demo3

struct TestNavigationWithStatusBar: View {
    var body: some View {
        NavigationView {
            Text("Hello, World!")
        }
        .statusBar(hidden: true)
    }
}

Upvotes: 2

Related Questions