8

I already spent a lot of hours trying to figure out a way to change statusBarStyle to light/dark using the new lifecycle SwiftUI App.

The newest posts about the status bar teach how to hide it, but I don't want to do it, I just need to change it to dark or light.

To change the color, the most recent way I found is open SceneDelegate.swift and change window.rootViewController to use my own HostingController, but it will only work for projects using UIKit App Delegate Lifecycle. Using SwiftUI App Lifecycle, the SceneDelegate.swift will not be generated, so where can I do it?

I can do it via General Settings on the Xcode interface. My question is about how to do it via code dynamically.

  • Target: iOS 14
  • IDE: Xcode 12 beta 3
  • OS: MacOS 11 Big Sur

Below is what I got so far.

Everything.swift

import Foundation
import SwiftUI

class LocalStatusBarStyle { // style proxy to be stored in Environment
    fileprivate var getter: () -> UIStatusBarStyle = { .default }
    fileprivate var setter: (UIStatusBarStyle) -> Void = {_ in}

    var currentStyle: UIStatusBarStyle {
        get { self.getter() }
        set { self.setter(newValue) }
    }
}

struct LocalStatusBarStyleKey: EnvironmentKey {
    static let defaultValue: LocalStatusBarStyle = LocalStatusBarStyle()
}

extension EnvironmentValues { // Environment key path variable
    var localStatusBarStyle: LocalStatusBarStyle {
        get {
            return self[LocalStatusBarStyleKey.self]
        }
    }
}

class MyHostingController<Content>: UIHostingController<Content> where Content:View {
    private var internalStyle = UIStatusBarStyle.default

    @objc override dynamic open var preferredStatusBarStyle: UIStatusBarStyle {
        get {
            internalStyle
        }
        set {
            internalStyle = newValue
            self.setNeedsStatusBarAppearanceUpdate()
        }
    }
    
    override init(rootView: Content) {
        super.init(rootView:rootView)

        LocalStatusBarStyleKey.defaultValue.getter = { self.preferredStatusBarStyle }
        LocalStatusBarStyleKey.defaultValue.setter = { self.preferredStatusBarStyle = $0 }
    }

    @objc required dynamic init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

struct TitlePage: View {
    @Environment(\.localStatusBarStyle) var statusBarStyle
    @State var title: String

    var body: some View {
        Text(title).onTapGesture {
            if self.statusBarStyle.currentStyle == .darkContent {
                self.statusBarStyle.currentStyle = .default
                self.title = "isDefault"
            } else {
                self.statusBarStyle.currentStyle = .darkContent
                self.title = "isDark"
            }
        }
    }
}

struct ContainerView: View {
    var controllers: [MyHostingController<TitlePage>]
    
    init(_ titles: [String]) {
        self.controllers = titles.map { MyHostingController(rootView: TitlePage(title: $0)) }
    }
    
    var body: some View {
        PageViewController(controllers: self.controllers)
    }
}

struct PageViewController: UIViewControllerRepresentable {
    var controllers: [UIViewController]
    
    func makeUIViewController(context: Context) -> UIPageViewController {
        let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)
        return pageViewController
    }
    
    func updateUIViewController(_ uiViewController: UIPageViewController, context: Context) {
        uiViewController.setViewControllers([controllers[0]], direction: .forward, animated: true)
    }
    
    typealias UIViewControllerType = UIPageViewController
}

MyApp.swift

import SwiftUI

@main
struct TestAppApp: App {
    var body: some Scene {
        WindowGroup {
            ContainerView(["Subscribe", "Comment"])
        }
    }
}

struct TestAppApp_Previews: PreviewProvider {
    static var previews: some View {
        Text("Hello, World!")
    }
}

enter image description here

Moacir Braga
  • 174
  • 1
  • 6
  • You can define your own `statusBarStyle` as an `@EnvironmentKey` property. This would be defined on your first view and all the child views will inherit this property. This answer might be helpful: https://stackoverflow.com/questions/59569503/swiftui-set-status-bar-color-for-a-specific-view – Christian Ray Leovido Jul 25 '20 at 22:22
  • Thank you for your comment. To execute what you are linking to, I need to have access to scene delegate and it is exactly what I'm saying that I don't have access because was not created. Do you know how to access this delegate without SceneDelegate.swift? – Moacir Braga Jul 25 '20 at 22:55
  • 1
    If you need some UIKit level customisations you should use UIKit Life Cycle. SwiftUI Life Cycle is not configurable at all, actually, for now. From behavior perspective there is no difference between these approaches, SwiftUI Life Cycle is just a built-in wrapper around scenes management. – Asperi Jul 26 '20 at 06:56
  • Asperi, I'm going to do what you said. I'll create a new project for iOS and a new one for MacOS using UIKit Life Cycle. SwiftUI Life Cycle is not ready, or its documentation is not good enough yet. – Moacir Braga Jul 26 '20 at 19:13
  • 1
    @MoacirBraga did you ever find a solution to change the status bar color programmatically using SwiftUI App Lifecycle? – alexexchanges Nov 25 '20 at 07:47
  • @jupiar and did you? – Orkhan Alikhanov Jan 22 '21 at 10:58
  • Have you tried my solution? I'm actually using this in my own app with the new lifecycle. – Burgler-dev Feb 23 '21 at 13:20
  • @jupiar I deleted my SwiftUI App Lifecycle, and I started a new project with UIKit App Delegate Lifecycle. Today, maybe with new answers posted here, one of them could work; I didn't try again. – Moacir Braga Feb 24 '21 at 14:09
  • @MoacirBraga can you mark Burgler-devs answer as solution for future people looking for the same answer? I tried it and its working for me. – Carsten Mar 10 '21 at 13:08
  • @Carsten, done. Thank you for your test and for notify me to mark it. – Moacir Braga Mar 30 '21 at 13:18

4 Answers4

6

Add two values to the Info.plist:

<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>

This works with the new SwiftUI App Lifecycle (@main). Verified on iOS14.4.

enter image description here

Burgler-dev
  • 230
  • 3
  • 7
1

My suggestion is you just create an AppDelegate Adaptor and do whatever customization you need from there. SwiftUI will handle the creation of AppDelegate and managing its lifetime.

Create an AppDelegate class:

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        UIApplication.shared.statusBarStyle = .darkContent
        return true
    }
}

Now inside your App:

@main
struct myNewApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            Text("I am a New View")
        }
    }
} 
Ajaz Ahmed
  • 11
  • 1
-1

This is not really a solution but the best I could come up with (and ended up doing) was to force app to the dark mode. Either in Info.plist or NavigationView { ... }.preferredColorScheme(.dark)

That will also change the statusBar. You will not be able to change the status bar style per View though.

-3

Before, with SceneDelegate

(code taken from SwiftUI: Set Status Bar Color For a Specific View)

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        ...
        window.rootViewController = MyHostingController(rootView: contentView)
}

After, with no SceneDelegate

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            MyHostingController(rootView: ContentView())
        }
    }
}

If you follow the answer from the link above and apply it here with the @main, you should be able to achieve your changes.

  • 2
    I think we are almost there. Doing it, the error message on WindowGroup is "Generic struct 'WindowGroup' requires that 'MyHostingController' conform to 'View'". What I need to do to make MyHostingController conform to View? – Moacir Braga Jul 25 '20 at 23:29