SwiftUI:状态栏颜色

时间:2019-07-16 18:08:23

标签: ios swift swiftui

是否可以将SwiftUI视图的状态栏更改为白色?

我可能缺少一些简单的东西,但似乎找不到在SwiftUI中将状态栏更改为白色的方法。到目前为止,我只看到.statusBar(hidden: Bool)

17 个答案:

答案 0 :(得分:21)

通过使用View设置.dark的{​​{1}}或.light模式配色方案,可以将状态栏的文本/淡色/前景颜色设置为白色。

层次结构中使用此方法的第一个视图优先。

例如:

.preferredColorScheme(_ colorScheme: ColorScheme?)
var body: some View {
  ZStack { ... }
  .preferredColorScheme(.dark) // white tint on status bar
}

答案 1 :(得分:12)

与链接的评论一样,我编辑了this question here

但是要回答这个问题并帮助人们直接找到答案:

Swift 5和SwiftUI

为SwiftUI创建一个名为HostingController.swift的新swift文件

import SwiftUI

class HostingController: UIHostingController<ContentView> {
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return .lightContent
    }
}

然后在SceneDelegate.swift中更改以下代码行

window.rootViewController = UIHostingController(rootView: ContentView())

window.rootViewController = HostingController(rootView: ContentView())

答案 2 :(得分:7)

在info.plist中,您可以简单地设置

  • 从“状态栏样式”到“轻内容”
  • “基于视图控制器的状态栏外观”为否

无需在您的代码中进行任何更改...

答案 3 :(得分:6)

此解决方案适用于使用新 SwiftUI 生命周期的应用:

我需要动态更改状态栏文本,但无法访问 window.rootViewController,因为 SwiftUI 生命周期中不存在 SceneDelegate

我终于找到了 Xavier Donnellon 的这个简单的解决方案:https://github.com/xavierdonnellon/swiftui-statusbarstyle

StatusBarController.swift 文件复制到您的项目中并将您的主视图包装到 RootView 中:

@main
struct ProjectApp: App {     
    var body: some Scene {
        WindowGroup {
            //wrap main view in RootView
            RootView {
                //Put the view you want your app to present here
                ContentView()
                    //add necessary environment objects here 
            }
        }
    }
}

然后您可以通过使用 .statusBarStyle(.darkContent).statusBarStyle(.lightContent) 视图修饰符,或通过调用例如更改状态栏文本颜色UIApplication.setStatusBarStyle(.lightContent) 直接。

不要忘记在 Info.plist 中将“基于视图控制器的状态栏外观”设置为“是”。

答案 4 :(得分:6)

现有答案涵盖了您只需要更改状态栏颜色一次的情况(例如,在整个应用中使用浅色内容),但是如果您要以编程方式进行操作,则可以使用首选项键来实现。 / p>

完整的示例可以在下面找到,但这是对我们将要做的事情的描述:

  • 定义一个符合"append"的结构,PreferenceKey将使用它来设置其首选的状态栏样式
  • 创建View的子类,该子类可以检测到首选项更改并将其桥接到相关的UIKit代码
  • 添加扩展名UIHostingController以获取几乎看起来正式的API

首选项键符合性

View

UIHostingController子类

struct StatusBarStyleKey: PreferenceKey {
  static var defaultValue: UIStatusBarStyle = .default
  
  static func reduce(value: inout UIStatusBarStyle, nextValue: () -> UIStatusBarStyle) {
    value = nextValue()
  }
}

查看扩展名

class HostingController: UIHostingController<AnyView> {
  var statusBarStyle = UIStatusBarStyle.default

  //UIKit seems to observe changes on this, perhaps with KVO?
  //In any case, I found changing `statusBarStyle` was sufficient
  //and no other method calls were needed to force the status bar to update
  override var preferredStatusBarStyle: UIStatusBarStyle {
    statusBarStyle
  }

  init<T: View>(wrappedView: T) {
// This observer is necessary to break a dependency cycle - without it 
// onPreferenceChange would need to use self but self can't be used until 
// super.init is called, which can't be done until after onPreferenceChange is set up etc.
    let observer = Observer()

    let observedView = AnyView(wrappedView.onPreferenceChange(StatusBarStyleKey.self) { style in
      observer.value?.statusBarStyle = style
    })

    super.init(rootView: observedView)
    observer.value = self
  }

  private class Observer {
    weak var value: HostingController?
    init() {}
  }

  @available(*, unavailable) required init?(coder aDecoder: NSCoder) {
    // We aren't using storyboards, so this is unnecessary
    fatalError("Unavailable")
  }
}

用法

首先,在您的extension View { func statusBar(style: UIStatusBarStyle) -> some View { preference(key: StatusBarStyleKey.self, value: style) } } 中,需要用子类替换SceneDelegate

UIHostingController

任何视图现在都可以使用您的扩展程序来指定其首选项:

//Previously: window.rootViewController = UIHostingController(rootView: rootView)
window.rootViewController = HostingController(wrappedView: rootView)

注释

this answer中,对另一个问题提出了使用HostingController子类观察首选项键变化的解决方案-我以前使用@EnvironmentObject,它有很多缺点,首选项键似乎更适合此问题。

这是解决此问题的正确方法吗?我不确定。在某些情况下,这可能无法解决,例如,如果层次结构中的多个视图指定了首选项键,我还没有进行全面的测试来查看哪个视图具有优先级。在我自己的用法中,我有两个相互排斥的视图,它们指定了它们首选的状态栏样式,因此我不必对此进行处理。因此,您可能需要对其进行修改以满足自己的需要(例如,可以使用元组同时指定样式和优先级,然后让VStack { Text("Something") }.statusBar(style: .lightContent) 在覆盖前先检查其优先级)。

答案 5 :(得分:5)

更新:看起来上述Hannes Sverrisson的答案是最接近的,但我们的答案略有不同。

上面写的关于UIHostingController子类的答案在XCode 11.3.1中不起作用。

以下对于子类(也处理ContentView环境设置)确实对我有用:

import SwiftUI

class HostingController<Content>: UIHostingController<Content> where Content : View {
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return .lightContent
    }
}

然后在SceneDelegate.swift中,确实更改window.rootViewController设置确实有效:

window.rootViewController = HostingController(rootView: contentView)

答案 6 :(得分:5)

这对我有用。将这些行添加到您的 info.plist 文件中。 您需要切换顶部设置 (View controller-based status bar appearance) 以确定您要查找的内容。

enter image description here

答案 7 :(得分:5)

SwiftUI

创建一个托管控制器DarkHostingController并在其上设置preferredStatusBarStyle

class DarkHostingController<Content> : UIHostingController<Content> where Content : View {
    @objc override dynamic open var preferredStatusBarStyle: UIStatusBarStyle {
        .lightContent
    }
}

并包裹在SceneDelegate中:

window.rootViewController = DarkHostingController(rootView: ContentView())

答案 8 :(得分:4)

如果您使用environmentObject,则可以使用this answer中提出的解决方案。

创建一个新文件并粘贴以下代码

import SwiftUI

class HostingController: UIHostingController<AnyView> {
   override var preferredStatusBarStyle: UIStatusBarStyle {
      return .lightContent
   }
}

这里的区别是我们使用AnyView而不是ContentView,从而可以替换以下内容:

window.rootViewController = UIHostingController(rootView:contentView.environmentObject(settings))

由此:

window.rootViewController = HostingController(rootView: AnyView(contentView.environmentObject(settings)))

答案 9 :(得分:3)

只需将其添加到info.plist

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

在IOS 14,xcode 12上进行了测试

答案 10 :(得分:1)

以上解决方案适用于状态栏样式。如果要将背景色应用于状态栏,则需要使用忽略顶部保存区域的VStack。

    GeometryReader{geometry in
        VStack{
            Rectangle().frame(width: geometry.size.width, height: 20, alignment: .center).foregroundColor(.red)
            Spacer()
            Your content view goes here
        }
        .frame(width: geometry.size.width, height: geometry.size.height)
    }.edgesIgnoringSafeArea(.top)

您可以使用实际的状态栏高度而不是固定的20。请参考以下链接以获取状态栏高度。 Status bar height in Swift

答案 11 :(得分:1)

@Dan Sandland的答案对我有用,但是在我的情况下,要求将界面保持在.light模式下

ZStack {
    Rectangle()...
    
    VStack(spacing: 0) {
        ...
    }.colorScheme(.light)
}
.preferredColorScheme(.dark)

答案 12 :(得分:0)

我正在使用类似

extension UIApplication {

    enum ColorMode {
        case dark, light
    }

    class func setStatusBarTextColor(_ mode: ColorMode) {
        if #available(iOS 13.0, *) {
            var style: UIUserInterfaceStyle
            switch mode {
            case .dark:
                style = .dark
            default:
                style = .light
            }
            if let window = Self.activeSceneDelegate?.window as? UIWindow {
                window.overrideUserInterfaceStyle = style
                window.setNeedsDisplay()
            }
        }
    }

    class var activeSceneDelegate: UIWindowSceneDelegate? {
        (Self.activeScene)?.delegate as? UIWindowSceneDelegate
    }
}

答案 13 :(得分:0)

创建一个名为HostingController.swift的新swift文件,或仅在现有的swift文件中添加此类。

class HostingController: UIHostingController<ContentView> {
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return .darkContent //or .lightContent

    }
}

然后在SceneDelegate.swift中更改代码行

window.rootViewController = UIHostingController(rootView: contentView)

window.rootViewController = HostingController(rootView: contentView)

答案 14 :(得分:0)

创建一个名为HostingController的新类:

import SwiftUI

final class HostingController<T: View>: UIHostingController<T> {
    override var preferredStatusBarStyle: UIStatusBarStyle {
        .lightContent
    }
}

在您的SceneDelegate.swift中,将所有出现的UIHostingController替换为HostingController

答案 15 :(得分:0)

Arkcann's answer 很棒,但不幸的是它对我不起作用,因为 StatusBarStyleKey.defaultValue 优先(我想知道他是如何管理它的)。我将其设为 Optional 并仅在明确设置时覆盖先前设置的值。 (我在 iOS 14.3 上的真实设备上进行测试)

struct StatusBarStyleKey: PreferenceKey {
  static func reduce(value: inout UIStatusBarStyle?, nextValue: () -> UIStatusBarStyle?) {
    guard let v = nextValue() else {
      return
    }
    
    value = v
  }
}

extension View {
  func statusBar(style: UIStatusBarStyle?) -> some View {
    return preference(key: StatusBarStyleKey.self, value: style)
  }
}

我在创建 HostingController 时也采用了一些不同的方法,我将状态栏样式存储在全局范围内。

private var appStatusBarStyle: UIStatusBarStyle?

private class HostingController<ContentView: View>: UIHostingController<ContentView> {
  override var preferredStatusBarStyle: UIStatusBarStyle {
    return appStatusBarStyle ?? .default
  }
}


func createHostingController<T: View>(rootView :T) -> UIViewController {
  let view = rootView.onPreferenceChange(StatusBarStyleKey.self) {
    appStatusBarStyle = $0
  }
  
  return HostingController(rootView: view)
}

用法:

window.rootViewController = createHostingController(rootView: MyApp())

答案 16 :(得分:0)

在所有提议的解决方案中,侵入性较小、最直接、实际上唯一对我们有用的是 Michał Ziobro 提出的解决方案: https://stackoverflow.com/a/60188583/944839

在我们的应用中,我们需要将屏幕显示为带有黑色状态栏的 sheet。两种简单的解决方案(如设置 preferredColorScheme)都不适合我们。但是,在屏幕的 onAppear 中手动强制应用配色方案以表格形式显示,然后在 onDisappear 中将其恢复为有效。

这是完整的扩展代码:

import SwiftUI
import UIKit

extension ColorScheme {
    var interfaceStyle: UIUserInterfaceStyle {
        switch self {
        case .dark: return .dark
        case .light: return .light
        @unknown default: return .light
        }
    }
}

extension SceneDelegate {
    static var current: Self? {
        let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
        return windowScene?.delegate as? Self
    }
}

extension UIApplication {
    static func setColorScheme(_ colorScheme: ColorScheme) {
        if let window = SceneDelegate.current?.window {
            window.overrideUserInterfaceStyle = colorScheme.interfaceStyle
            window.setNeedsDisplay()
        }
    }
}

PS 为了让屏幕本身仍然使用 light 配色方案,我们将 colorScheme(.light) 修饰符应用于 body 的内容。