是否可以将SwiftUI视图的状态栏更改为白色?
我可能缺少一些简单的东西,但似乎找不到在SwiftUI中将状态栏更改为白色的方法。到目前为止,我只看到.statusBar(hidden: Bool)
。
答案 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
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)
答案 7 :(得分:5)
创建一个托管控制器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
的内容。