SwiftUI导航栏按钮的颜色会根据根视图是ScrollView还是VStack而改变
我在一个 NavigationStack 里放着一个SwiftUI视图,并使用了自定义导航栏背景颜色。我希望导航栏按钮在整个应用中保持一致的颜色。
问题在于导航栏按钮的颜色会根据body中的首个根视图而变化。
当根视图是一个 ScrollView 时,导航栏按钮显示为白色:
var body: some View {
ScrollView {}
.toolbarBackground(Color(red: 0.02, green: 0.27, blue: 0.13), for: .navigationBar)
.toolbarBackground(.visible, for: .navigationBar)
.toolbarColorScheme(.dark, for: .navigationBar)
}

然而,如果我把 ScrollView 替换为 VStack,并保持相同的修饰符,导航栏按钮却显示为黑色:
var body: some View {
VStack {}
.toolbarBackground(Color(red: 0.02, green: 0.27, blue: 0.13), for: .navigationBar)
.toolbarBackground(.visible, for: .navigationBar)
.toolbarColorScheme(.dark, for: .navigationBar)
}
}

唯一的变化是根视图的类型。导航栏背景颜色是相同的,应用的修饰符也相同。
有没有办法在不同根视图之间让导航栏按钮的颜色保持一致?
例如,应该将修饰符应用于 NavigationStack,根内容视图,还是应用于每个独立的屏幕?还是需要其他SwiftUI修饰符 / UIKit外观设置来强制导航栏按钮的颜色?
如何让导航栏按钮在这两种情况下都保持相同的颜色?
解决方案
苹果的 工具栏最佳实践指南 警告不要为工具栏设置背景色,因为这可能会“干扰背景效果”:
尽量减少对工具栏背景和着色控件的使用。 你使用的任何自定义背景和外观可能会覆盖或干扰系统提供的背景效果。相反,使用内容层来指示工具栏的颜色和外观...
这种“干扰”很可能就是你示例中发生的情况。
因此,按照 使用内容层来指示工具栏的颜色和外观 的建议,可以通过在主内容上应用一个 .background 修饰符来实现背景。随后就可以移除 .toolbarBackground 的修饰符。
下面是你的示例如何更新以这种方式工作:
NavigationStack {
NavigationLink("Go to Settings") {
ScrollView {}
.navigationTitle("Settings")
.navigationBarTitleDisplayMode(.inline)
.toolbarColorScheme(.dark, for: .navigationBar)
.background(alignment: .top) {
Color(red: 0.02, green: 0.27, blue: 0.13)
.ignoresSafeArea()
.frame(height: 0)
}
}
}
你会注意到,在忽略安全区域之后再应用一个高度为0 的帧。这样,只有安全区域的边距被填充为该颜色。
对于一个 VStack 的情况,使用 .frame 修饰符将 VStack 扩展到屏幕的全尺寸:
VStack {}
.frame(maxWidth: .infinity, maxHeight: .infinity)
// + other modifiers, as for ScrollView
使用这种方法时,返回按钮在两种情况下的外观会保持一致。然而,当过渡正在发生时,尤其是在首次执行过渡时,你有时会看到它从明亮的外观变为较暗的外观:

我没有找到一个简单的方法来阻止这种闪烁。一个可行的做法是在初次显示时隐藏返回按钮,然后再延迟显示,但这似乎是一个相当复杂的权宜之计。因此,如果你想避免闪烁且找不到满意的解决方法,我建议发一篇新帖子,专门聚焦于这个具体问题。