如何在SwiftUI应用程序生命周期中更改StatusBarStyle?

8

我已经花费很多时间试图找到一种使用新的生命周期SwiftUI App更改statusBarStyle为浅色/深色的方法。

最新有关状态栏的帖子介绍了如何隐藏它,但我不想这样做,我只需要将其更改为深色或浅色。

要更改颜色,我发现最近的方法是打开SceneDelegate.swift并将window.rootViewController更改为使用自己的HostingController,但它仅适用于使用UIKit App Delegate Lifecycle的项目。使用SwiftUI App LifecycleSceneDelegate.swift将不会生成,那么我可以在哪里做到呢?

我可以通过Xcode界面上的常规设置来完成。我的问题是如何通过代码动态地完成它。

  • 目标:iOS 14
  • IDE:Xcode 12 beta 3
  • 操作系统:MacOS 11 Big Sur

以下是我目前所拥有的。

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


1
如果您需要一些UIKit级别的自定义,您应该使用UIKit生命周期。实际上,目前SwiftUI生命周期是不可配置的。从行为角度来看,这些方法之间没有区别,SwiftUI生命周期只是场景管理的内置包装器。 - Asperi
1
@MoacirBraga,您是否找到了使用SwiftUI应用程序生命周期以编程方式更改状态栏颜色的解决方案? - alexexchanges
你试过我的解决方案吗?我实际上正在我的应用程序中使用它与新的生命周期。 - Burgler-dev
@jupiar 我删除了我的SwiftUI应用程序生命周期,并使用UIKit应用程序委托生命周期启动了一个新项目。也许今天在这里发布的新答案之一可以起作用;我还没有再试过。 - Moacir Braga
@Carsten,已完成。感谢您的测试并通知我标记它。 - Moacir Braga
显示剩余5条评论
4个回答

6
将两个值添加到Info.plist中:
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>

这适用于新的SwiftUI应用程序生命周期(@main)。在iOS14.4上经过验证。

enter image description here


这个可行,应该被接受为答案。我遇到了完全相同的问题。非常感谢。 - Carsten
不支持iOS16-17版本。 - undefined

1
我的建议是创建一个AppDelegate适配器,并从那里进行任何自定义。SwiftUI将处理创建AppDelegate并管理其生命周期的工作。
创建一个AppDelegate类:
class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        UIApplication.shared.statusBarStyle = .darkContent
        return true
    }
}

现在在您的应用程序中:

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

-1

这并不是一个真正的解决方案,但我能想到的最好的方法(并最终采用的方法)是强制应用程序进入深色模式。可以在 Info.plist 或 NavigationView { ... }.preferredColorScheme(.dark) 中实现。

这也会改变状态栏。但您将无法针对每个视图更改状态栏样式。


-3

以前,使用 SceneDelegate

(代码取自SwiftUI:为特定视图设置状态栏颜色)

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

在没有 SceneDelegate 的情况下

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

如果你按照上面链接中的答案并在这里应用@main,你应该能够实现你的更改。


2
我认为我们快要完成了。在执行时,WindowGroup的错误信息是“通用结构'WindowGroup'要求'MyHostingController<ContentView>'符合'View'”。我需要做什么来使MyHostingController符合View? - Moacir Braga

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接