如何在 SwiftUI 应用生命周期中更改状态栏样式?

Posted

技术标签:

【中文标题】如何在 SwiftUI 应用生命周期中更改状态栏样式?【英文标题】:How to change StatusBarStyle on SwiftUI App Lifecycle? 【发布时间】:2020-11-15 11:15:12 【问题描述】:

我已经花了很多时间试图找出一种方法,使用新的生命周期 SwiftUI 应用程序statusBarStyle 更改为亮/暗。

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

要更改颜色,我发现的最新方法是打开 SceneDelegate.swift 并更改 window.rootViewController 以使用我自己的 HostingController ,但它仅适用于使用 UIKit App Delegate Lifecycle 的项目。使用SwiftUI App Lifecycle,不会生成SceneDelegate.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!")
    

【问题讨论】:

您可以将自己的statusBarStyle 定义为@EnvironmentKey 属性。这将在您的第一个视图上定义,所有子视图都将继承此属性。这个答案可能会有所帮助:***.com/questions/59569503/… 感谢您的评论。要执行您链接的内容,我需要访问场景委托,这正是我所说的我无权访问,因为没有创建。你知道如何在没有 SceneDelegate.swift 的情况下访问这个委托吗? 如果你需要一些 UIKit 级别的自定义,你应该使用 UIKit 生命周期。实际上,目前 SwiftUI 生命周期根本无法配置。从行为的角度来看,这些方法之间没有区别,SwiftUI Life Cycle 只是场景管理的内置包装器。 Asperi,我会照你说的做。我将使用 UIKit 生命周期为 iOS 创建一个新项目,并为 MacOS 创建一个新项目。 SwiftUI Life Cycle 没有准备好,或者文档还不够完善。 @MoacirBraga 你有没有找到使用 SwiftUI App Lifecycle 以编程方式更改状态栏颜色的解决方案? 【参考方案1】:

向 Info.plist 添加两个值:

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

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

【讨论】:

这是有效的,应该是公认的答案。我有完全相同的问题。非常感谢【参考方案2】:

我的建议是您只需创建一个 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")
        
    
 

【讨论】:

【参考方案3】:

这并不是一个真正的解决方案,但我能想到的最好的(并最终做的)就是强制应用程序进入暗模式。在 Info.plist 或 NavigationView ... .preferredColorScheme(.dark)

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

【讨论】:

【参考方案4】:

之前,SceneDelegate

(代码取自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)

之后,没有SceneDelegate

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

如果您按照上面链接中的答案并在此处使用@main 应用它,您应该能够实现您的更改。

【讨论】:

我想我们快到了。这样做,WindowGroup 上的错误消息是“通用结构 'WindowGroup' 要求 'MyHostingController' 符合 'View'”。我需要做什么才能使 MyHostingController 符合 View?

以上是关于如何在 SwiftUI 应用生命周期中更改状态栏样式?的主要内容,如果未能解决你的问题,请参考以下文章

如何更改应用程序色调颜色(新的 SwiftUI 生命周期应用程序)?

如何在 Xcode 12 的新 SwiftUI App 生命周期中更改 window.rootViewController?

如何在新的 SwiftUI 应用生命周期中接受 CloudKit 共享?

如何将 CoreSpotlight 与 Swiftui 应用生命周期进行深度链接?

SwiftUI App入口生命周期管理方式(Life Cycle)

SwiftUI |如何覆盖 viewWillAppear 等生命周期方法