我可能忽略了一些简单的内容,但我似乎找不到在SwiftUI中将状态栏更改为白色的方法。目前我只看到
.statusBar(hidden: Bool)
。通过使用 .preferredColorScheme(_ colorScheme: ColorScheme?)
方法,可以将状态栏文本/色调/前景颜色设置为白色,方法是设置 View
的 .dark
或 .light
模式颜色方案。
使用此方法的层次结构中的第一个视图将优先生效。
例如:
var body: some View {
ZStack { ... }
.preferredColorScheme(.dark) // white tint on status bar
}
var body: some View {
ZStack { ... }
.preferredColorScheme(.light) // black tint on status bar
}
如同链接评论中所述,我编辑了这个问题
但是为了回答这个问题并帮助人们直接找到答案:
Swift 5和SwiftUI
对于SwiftUI,请创建一个名为HostingController.swift的新的swift文件。
import SwiftUI
class HostingController<ContentView>: UIHostingController<ContentView> where ContentView : View {
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}
然后在SceneDelegate.swift文件中更改以下代码行
window.rootViewController = UIHostingController(rootView: ContentView())
to
window.rootViewController = HostingController(rootView: ContentView())
只需将此添加到 info.plist 中即可。
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
在 iOS 14、Xcode 12 上进行了测试。
properties:
UIStatusBarStyle: UIStatusBarStyleLightContent
UIViewControllerBasedStatusBarAppearance: false
- Reza Dehnavi这个解决方案适用于使用新的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中将“View controller-based status bar appearance”设置为“YES”。
Unbalanced calls to begin/end appearance transitions for <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x14cf0e3a0>.
。有人知道如何解决吗? - wristbandsPreferenceKey
的结构体,这将由View
用于设置其首选状态栏样式UIHostingController
的子类,该子类可以检测到首选项更改并将其与相关的UIKit代码进行桥接View
扩展以获得几乎看起来正式的APIstruct 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)
现在,任何视图都可以使用您的扩展来指定其首选项:
VStack {
Text("Something")
}.statusBar(style: .lightContent)
在这个答案中,使用HostingController子类来观察首选项键的更改是一个解决方案。我之前使用了@EnvironmentObject,但它有很多缺点,而首选项键似乎更适合这个问题。
这是解决这个问题的正确方法吗?我不确定。可能会有一些边缘情况无法处理,例如,如果层次结构中有多个视图指定了首选项键,那么我没有进行全面测试以查看哪个视图具有优先级。在我的使用中,我有两个互斥的视图指定其首选状态栏样式,因此我没有遇到过这种情况。因此,您可能需要修改此内容以适应您的需求(例如,可以使用元组来指定样式和优先级,然后让您的HostingController
在覆盖之前检查其先前的优先级)。
PreferenceKey.reduce
方法中定义自定义逻辑,以使它变得如你所愿。如果您想要更深入的值优先,则可以将值附加到数组并仅使用第一个(或最后一个)项目。 - Orkhan AlikhanovSceneDelegate
和 AppDelegate
,并将 AppDelegate
放入你的 App
中,并将 SceneDelegate
连接到 AppDelegate
。
这对其他问题也起作用。我需要在单个视图中动态切换状态栏文本颜色,因此我将尝试这个方法。 - David Reich创建一个托管控制器DarkHostingController
,并在其中设置preferredStatusBarStyle
:
class DarkHostingController<ContentView> : UIHostingController<ContentView> where ContentView : View {
override dynamic open var preferredStatusBarStyle: UIStatusBarStyle {
.lightContent
}
}
并将其包装在SceneDelegate
中:
window.rootViewController = DarkHostingController(rootView: ContentView())
@Dan Sandland的答案对我有用,但在我的情况下需要保持界面处于.light
模式。
ZStack {
Rectangle()...
VStack(spacing: 0) {
...
}.colorScheme(.light)
}
.preferredColorScheme(.dark)
import SwiftUI
class HostingController<Content>: UIHostingController<Content> where Content : View {
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}
window.rootViewController
的设置更改为以下内容确实有效:window.rootViewController = HostingController(rootView: contentView)
UIApplication.shared.statusBarStyle = .lightContent
,但在 Xcode 中会收到警告,因为这在 iOS 13 中已被弃用。但是当我尝试在窗口场景上设置此属性时:windowScene.statusBarManager?.statusBarStyle = .light
,我会收到一个错误:Cannot assign to property: 'statusBarStyle' is a get-only property
。 - keegan3dUIViewControllerBasedStatusBarAppearance
为YES
,可以使用preferredStatusBarStyle
方法来更改状态栏的文本颜色。但是,这种方法只适用于单个视图控制器,并且在导航控制器等其他控制器中无效。要解决这个问题,您需要在每个视图控制器中实现preferredStatusBarStyle
方法,或者您可以创建一个基类视图控制器并在其中实现该方法,然后使所有其他视图控制器继承自该基类。 - Vlad Lego