SwiftUI 更新导航栏标题颜色

135

如何在SwiftUI中更改导航栏标题颜色

NavigationView {
    List {
        ForEach(0..<15) { item in
            HStack {
                Text("Apple")
                    .font(.headline)
                    .fontWeight(.medium)
                    .color(.orange)
                    .lineLimit(1)
                    .multilineTextAlignment(.center)
                    .padding(.leading)
                    .frame(width: 125, height: nil)
                
                Text("Apple Infinite Loop. Address: One Infinite Loop Cupertino, CA 95014 (408) 606-5775 ")
                    .font(.subheadline)
                    .fontWeight(.regular)
                    .multilineTextAlignment(.leading)
                    .lineLimit(nil)
            }
        }
    }
    .navigationBarTitle(Text("TEST")).navigationBarHidden(false).foregroundColor(.orange)
}

我尝试过.foregroundColor(.orange)但是没有效果

还尝试了.navigationBarTitle(Text("TEST").color(.orange))


3
嗯...看起来SwiftUI忽略了为导航栏标题设置的所有修饰符...而且很奇怪的是我们无法在导航栏中放置任何视图 :-( - Olexiy Pyvovarov
遇到了类似的问题,并通过使用 ZStack 并将其内部的 Rectangle 偏移至导航区域下方屏幕外找到了一种 hacky 的方法。请查看 - https://dev59.com/gdL7oIgBc1ULPQZFtM6a#75278773 - ansavchenco
28个回答

174

不需要使用.appearance()来全局设置样式。

虽然SwiftUI没有直接暴露导航栏的样式,但是可以通过使用UIViewControllerRepresentable进行绕过。由于SwiftUI在幕后使用常规的UINavigationController,因此视图控制器仍将具有有效的.navigationController属性。

struct NavigationConfigurator: UIViewControllerRepresentable {
    var configure: (UINavigationController) -> Void = { _ in }

    func makeUIViewController(context: UIViewControllerRepresentableContext<NavigationConfigurator>) -> UIViewController {
        UIViewController()
    }
    func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<NavigationConfigurator>) {
        if let nc = uiViewController.navigationController {
            self.configure(nc)
        }
    }

}

并且要使用它

struct ContentView: View {
    var body: some View {
        NavigationView {
            ScrollView {
                Text("Don't use .appearance()!")
            }
            .navigationBarTitle("Try it!", displayMode: .inline)
            .background(NavigationConfigurator { nc in
                nc.navigationBar.barTintColor = .blue
                nc.navigationBar.titleTextAttributes = [.foregroundColor : UIColor.white]
            })
        }
    .navigationViewStyle(StackNavigationViewStyle())
    }
}

Modified navigation bar


23
即使在子视图中声明了.background,它也不能用于子视图,在这种情况下,导航栏保持相同的颜色。 - kdion4891
20
如果这是场景代理中设置的初始视图,它似乎第一次不起作用;在闭包中,vc.navigationController为nil。当我从某个地方呈现一个VC时,它立即重新加载并具有适当的样式... - Josh
4
不幸的是,它不能与显示模式一起使用。 - orafaelreis
13
在https://filipmolcik.com/navigationview-dynamic-background-color-in-swiftui/中所描述的使用ViewModifier替代UIViewControllerRepresentable解决了许多问题,比如scrollView重叠、颜色在第一次加载时未生效、半透明效果和间歇性性能问题。 - gyleg5
7
最新的解决方案,我也像其他人一样得到了vc.navigationController为空的问题,我完美地复制了所有代码以确保没有错误,但是导航栏标题颜色和导航栏条形颜色仍然没有改变... 然后@gyleg5的答案解决了我的问题。此来源 - Farras Doko
显示剩余22条评论

56
在SwiftUI中,您无法直接更改导航标题颜色。您需要在init()中更改UINavigation的外观,例如:
struct YourView: View {

    init() {
        //Use this if NavigationBarTitle is with Large Font
        UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.red]

        //Use this if NavigationBarTitle is with displayMode = .inline
        UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: UIColor.red]
    }

    var body: some View {

        NavigationView {
            List{
                ForEach(0..<15) { item in
                    HStack {
                        Text("Apple")
                            .font(.headline)
                            .fontWeight(.medium)
                            .color(.orange)
                            .lineLimit(1)
                            .multilineTextAlignment(.center)
                            .padding(.leading)
                            .frame(width: 125, height: nil)


                        Text("Apple Infinite Loop. Address: One Infinite Loop Cupertino, CA 95014 (408) 606-5775 ")
                            .font(.subheadline)
                            .fontWeight(.regular)
                            .multilineTextAlignment(.leading)
                            .lineLimit(nil)
                    }
                }
            }
            .navigationBarTitle(Text("TEST")).navigationBarHidden(false)
            //.navigationBarTitle (Text("TEST"), displayMode: .inline)
        }
    }
}

我希望它能够工作。谢谢!!


1
您知道如何在WatchOS上使用SwiftUI实现这个吗? - 39fredy
1
我遇到了一个错误:使用未解决的标识符'UINavigationBar' - 39fredy
NavigationView 在 watchOS 中不可用。 - Anjali Kevadiya
请查看您的帖子 https://dev59.com/-lMH5IYBdhLWcg3w1TuK ,我在那里回答了如何更改它。 - Anjali Kevadiya
13
这将在全局范围内生效,并影响应用程序中的所有其他视图。 - kdion4891
显示剩余3条评论

51

我已经搜索了这个问题,并找到了一篇很棒的文章,你可以将导航栏样式的设置作为视图修饰符进行包装。

请查看此链接

注:我认为您需要在这个示例中更新一些代码,添加titleColor参数。

struct NavigationBarModifier: ViewModifier {

    var backgroundColor: UIColor?
    var titleColor: UIColor?

    init(backgroundColor: UIColor?, titleColor: UIColor?) {
        self.backgroundColor = backgroundColor
        let coloredAppearance = UINavigationBarAppearance()
        coloredAppearance.configureWithTransparentBackground()
        coloredAppearance.backgroundColor = backgroundColor
        coloredAppearance.titleTextAttributes = [.foregroundColor: titleColor ?? .white]
        coloredAppearance.largeTitleTextAttributes = [.foregroundColor: titleColor ?? .white]

        UINavigationBar.appearance().standardAppearance = coloredAppearance
        UINavigationBar.appearance().compactAppearance = coloredAppearance
        UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
    }

    func body(content: Content) -> some View {
        ZStack{
            content
            VStack {
                GeometryReader { geometry in
                    Color(self.backgroundColor ?? .clear)
                        .frame(height: geometry.safeAreaInsets.top)
                        .edgesIgnoringSafeArea(.top)
                    Spacer()
                }
            }
        }
    }
}

extension View {

    func navigationBarColor(backgroundColor: UIColor?, titleColor: UIColor?) -> some View {
        self.modifier(NavigationBarModifier(backgroundColor: backgroundColor, titleColor: titleColor))
    }

}

之后,像这样应用:

.navigationBarColor(backgroundColor: .clear, titleColor: .white)

我希望它能够工作。


2
谢谢你的解决方案 :) 真是太棒了!但是当导航栏标题切换为内联外观时,导航栏高度仍然不会减少?你有尝试过任何解决方法吗?提前感谢 :D - user2580
2
这应该是被接受的答案。使用UIViewControllerRepresentable会导致许多问题,例如重叠的scrollView和有时根本不改变颜色。 - gyleg5
1
这太棒了!原始文章应该使用coloredAppearance.backgroundColor = backgroundColor而不是coloredAppearance.backgroundColor = .clear,就像你所做的那样--因为橡皮筋滚动效果。 - aheze
1
这个解决方案非常适用于修改全局导航栏。有人能够使用这个解决方案在导航到子视图时改变样式吗? - deltatango
4
这将在全局应用颜色。干脆在AppDelegate中做这个,避免在这里整个过程中搞乱了。 - gm_
显示剩余2条评论

42
在 iOS 14 中,SwiftUI 提供了一种使用新的 toolbar 修饰符自定义导航栏的方式。我们需要将 ToolbarItem 的放置类型设置为 .principal,并将其设置为新的 toolbar 修饰符。您甚至可以设置图像等其他内容。
NavigationView {
    Text("My View!")
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItem(placement: .principal) {
                HStack {
                    Image(systemName: "sun.min.fill")
                    Text("Title")
                        .font(.headline)
                        .foregroundColor(.orange)
                }
            }
        }
}

15
但是您不能够设置工具栏的背景颜色,对吗? - hkdalex
1
这仅适用于 inline 显示模式,就像您在此处一样,但不适用于 large - vollkorntomate
11
这并没有回答这个问题。 - leonboe1

24

我采取了稍微不同的方法; 我只想改变标题文字的颜色,而不是NavigationBar的其他任何内容。参考上述和这篇文章的灵感后,我得出了:

import SwiftUI

extension View {
    /// Sets the text color for a navigation bar title.
    /// - Parameter color: Color the title should be
    ///
    /// Supports both regular and large titles.
    @available(iOS 14, *)
    func navigationBarTitleTextColor(_ color: Color) -> some View {
        let uiColor = UIColor(color)
    
        // Set appearance for both normal and large sizes.
        UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: uiColor ]
        UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: uiColor ]
    
        return self
    }
}

由于UIColor.init(_ color: Color)需要iOS 14,因此需要iOS 14。

可以这样利用:

struct ExampleView: View {
    var body: some View {
        NavigationView {
            Text("Hello, World!")
                .navigationBarTitle("Example")
                .navigationBarTitleTextColor(Color.red)
        }
    }
}

这反过来产生:

红色导航标题


请问我应该在哪里调用这个函数?它给我报错“无法在作用域中找到'self'”。 - Abrcd18
喜爱这个解决方案的优雅和效果! - Jorge Vicente Mendoza

24

在 Arsenius 的回答基础上,我发现一个优雅的方法来保证它能够始终正常工作,那就是继承 UIViewController 并在 viewDidLayoutSubviews() 中进行配置。

用法:

VStack {
    Text("Hello world")
        .configureNavigationBar {
            $0.navigationBar.setBackgroundImage(UIImage(), for: .default)
            $0.navigationBar.shadowImage = UIImage()
        }
}

实现:

extension View {
    func configureNavigationBar(configure: @escaping (UINavigationController) -> Void) -> some View {
        modifier(NavigationConfigurationViewModifier(configure: configure))
    }
}

struct NavigationConfigurationViewModifier: ViewModifier {
    let configure: (UINavigationController) -> Void

    func body(content: Content) -> some View {
        content.background(NavigationConfigurator(configure: configure))
    }
}

struct NavigationConfigurator: UIViewControllerRepresentable {
    let configure: (UINavigationController) -> Void

    func makeUIViewController(
        context: UIViewControllerRepresentableContext<NavigationConfigurator>
    ) -> NavigationConfigurationViewController {
        NavigationConfigurationViewController(configure: configure)
    }

    func updateUIViewController(
        _ uiViewController: NavigationConfigurationViewController,
        context: UIViewControllerRepresentableContext<NavigationConfigurator>
    ) { }
}

final class NavigationConfigurationViewController: UIViewController {
    let configure: (UINavigationController) -> Void

    init(configure: @escaping (UINavigationController) -> Void) {
        self.configure = configure
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        if let navigationController = navigationController {
            configure(navigationController)
        }
    }
}

这确实解决了当导航控制器在第一次加载时为nil的问题。谢谢! - Salavat Khanov
2
这绝对运行得更好。在iOS 14上进行了测试。 - Burgler-dev
很高兴能够帮助!! - EngageTheWarpDrive
有没有办法让这个与.navigationBarTitleDisplayMode(.automatic)一起工作?它似乎只能与.large.inline一起使用 - 当设置为自动时,从大到内联的转换永远不会发生。 - Ben
1
显然,从XCode 13开始,在iOS 14上viewDidLayoutSubviews不起作用,但viewWillAppear可以。 ‍♂️ - Bioche
这个很好用,如果需要的话,你也可以使用 viewWillDisappear 来重置任何配置。 - robinst

10

Demo

iOS 14 开始,你可以拥有任何自定义视图(包括自定义文本、自定义颜色和字体)

.navigationBarTitleDisplayMode(.inline)
.toolbar {
    ToolbarItem(placement: .principal) {
        VStack {
            Text("Yellow And Bold Title")
                .bold()
                .foregroundColor(.yellow)
        }
    }
}

同时,您可以像以下代码一样从 iOS 16 设置导航栏颜色:

.toolbarBackground(.visible, for: .navigationBar)
.toolbarBackground(.red, for: .navigationBar)

2
是的,但如果您需要支持iOS 14、15和16呢?您的示例如何应用于16之前的版本中使用红色? - Jonauz
你应该采用 外观模式 的解决方案,@Jonauz。 - Mojtaba Hosseini

10

不要使用 appearance() 方法来影响所有导航栏的样式,您可以使用 SwiftUI-Introspect 分别设置它们。

示例:

struct ContentView: View {
    var body: some View {
        NavigationView {
            ScrollView {
                Text("Hello world!")
            }
            .navigationTitle("Title")
        }
        .introspectNavigationController { nav in
            nav.navigationBar.barTintColor = .systemBlue
        }
    }
}

结果:

结果


这实际上是一个很好的建议,谢谢!我花了几天时间寻找一种方便的方法来自定义NavigationView标题,而这个方法非常不错。 - Georgi

8
使用以下代码可在SwiftUI中进行颜色定制
这是主体背景颜色的代码:
struct ContentView: View {

var body: some View {

 Color.red
.edgesIgnoringSafeArea(.all)

 }

}

在这里输入图片描述

导航栏:

struct ContentView: View {

@State var msg = "Hello SwiftUI"

init() {

    UINavigationBar.appearance().backgroundColor = .systemPink

     UINavigationBar.appearance().largeTitleTextAttributes = [
        .foregroundColor: UIColor.white,
               .font : UIFont(name:"Helvetica Neue", size: 40)!]

}

var body: some View {

    NavigationView {

    Text(msg)

        .navigationBarTitle(Text("NAVIGATION BAR"))

    }

    }

  }

这里输入图片描述

其他 UI 元素颜色定制

struct ContentView: View {

@State var msg = "Hello SwiftUI"

var body: some View {

        Text(msg).padding()
            .foregroundColor(.white)
            .background(Color.pink)

    }
 }

enter image description here


谢谢,设置largeTitleTextAttributes对我有用。 - Gamma-Point

7

我已经开发了一个小的自定义SwiftUI导航示例,可以提供完整的可视化自定义和编程式导航,可以用作NavigationView的替代。

这是NavigationStack类,处理currentView和导航栈:

final class NavigationStack: ObservableObject  {
    @Published var viewStack: [NavigationItem] = []
    @Published var currentView: NavigationItem

    init(_ currentView: NavigationItem ){
        self.currentView = currentView
    }

    func unwind(){
        if viewStack.count == 0{
            return
        }

        let last = viewStack.count - 1
        currentView = viewStack[last]
        viewStack.remove(at: last)
    }

    func advance(_ view:NavigationItem){
        viewStack.append( currentView)
        currentView = view
    }

    func home( ){
        currentView = NavigationItem( view: AnyView(HomeView()))
        viewStack.removeAll()
    }
}


您可以看一下这里的完整示例和说明:链接
PS:我不确定为什么这个被删除了。我认为它是一个完美的功能替代NavigationView来回答问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接