在SwiftUI中动态改变和更新导航栏的颜色

3
我正在尝试在SwiftUI中更改NavigationBar的背景颜色,我已经使用以下代码完成了这个任务。但是,我还没有弄清楚如何动态更改和更新导航栏的背景颜色。
struct ContentView: View {
    @State var color: Color = .red
    
    init() {
        let navbarAppearance = UINavigationBarAppearance()
        navbarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
        navbarAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
        navbarAppearance.backgroundColor = UIColor(color)
        UINavigationBar.appearance().standardAppearance = navbarAppearance
        UINavigationBar.appearance().compactAppearance = navbarAppearance
        UINavigationBar.appearance().scrollEdgeAppearance = navbarAppearance
        
    }
    
    var body: some View {
        NavigationView {
                VStack(spacing: 20) {
                    Button(action: { color = .blue }) {
                        Text("Blue")
                            .font(.title)
                            .bold()
                            .foregroundColor(.white)
                            .frame(width: 100)
                            .padding()
                            .background(Color.blue)
                            .cornerRadius(15)
                    }
                    Button(action: { color = .red }) {
                        Text("Red")
                            .font(.title)
                            .bold()
                            .foregroundColor(.white)
                            .frame(width: 100)
                            .padding()
                            .background(Color.red)
                            .cornerRadius(15)
                    }
                }
                .offset(y: -50)
                .navigationTitle("My Navigation")
        }
    }
    
}

这段代码给出了正确的结果,但是点击其中一个按钮来更改“Color”变量并不会更新“NavigationBar”的颜色。这是因为在初始化时,导航栏保留了所有的特性,所以我需要找到一种方法在之后更改这些特性,并且如果可能的话,在更改颜色时进行动画过渡。谢谢任何帮助!
2个回答

6
您可以使用SwiftUI-Introspect,这样您只需更改此NavigationView的导航栏。它不会影响任何其他实例。

您也不需要使用.id(...),因为当视图更改标识时,可能会破坏动画,不必要地重新初始化视图,破坏视图生命周期等。

在我的示例中,您保存了UINavigationController的当前实例。当color的值更改时,再次设置导航栏的外观。

使用Introspect的示例:

import Introspect

/* ... */

struct ContentView: View {
    @State private var color: Color = .red
    @State private var nav: UINavigationController?

    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
                Button(action: { color = .blue }) {
                    Text("Blue")
                        .font(.title)
                        .bold()
                        .foregroundColor(.white)
                        .frame(width: 100)
                        .padding()
                        .background(Color.blue)
                        .cornerRadius(15)
                }

                Button(action: { color = .red }) {
                    Text("Red")
                        .font(.title)
                        .bold()
                        .foregroundColor(.white)
                        .frame(width: 100)
                        .padding()
                        .background(Color.red)
                        .cornerRadius(15)
                }
            }
            .offset(y: -50)
            .navigationTitle("My Navigation")
        }
        .introspectNavigationController { nav in
            self.nav = nav
            updateNavBar()
        }
        .onChange(of: color) { _ in
            updateNavBar()
        }
    }

    private func updateNavBar() {
        guard let nav = nav else { return }
        let navbarAppearance = UINavigationBarAppearance()
        navbarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
        navbarAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
        navbarAppearance.backgroundColor = UIColor(color)
        nav.navigationBar.standardAppearance = navbarAppearance
        nav.navigationBar.compactAppearance = navbarAppearance
        nav.navigationBar.scrollEdgeAppearance = navbarAppearance
    }
}

结果:

结果


非常感谢!这绝对是我在寻找的,我特别喜欢它只影响我应用它的导航视图实例。现在唯一剩下的事情就是弄清楚是否可以动画化这个过渡...如果可以的话。 - calebm

3

是的,外观会应用于所有在其之后创建的视图。因此,在更改颜色时,我们需要重置外观然后重新创建NavigationView

这是可能方法的演示(已在 Xcode 13 / iOS 15 上进行测试)

演示

struct ContentView: View {
    @State var color: Color = .red

    var body: some View {
        MainNavView(barColor: $color)
            .id(color)                // << re-creates !!
    }
}

struct MainNavView: View {
    @Binding var color: Color
    init(barColor: Binding<Color>) {
        self._color = barColor
        let navbarAppearance = UINavigationBarAppearance()
        navbarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
        navbarAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
        navbarAppearance.backgroundColor = UIColor(color)
        UINavigationBar.appearance().standardAppearance = navbarAppearance
        UINavigationBar.appearance().compactAppearance = navbarAppearance
        UINavigationBar.appearance().scrollEdgeAppearance = navbarAppearance

    }

    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
                Button(action: { color = .blue }) {
                    Text("Blue")
                        .font(.title)
                        .bold()
                        .foregroundColor(.white)
                        .frame(width: 100)
                        .padding()
                        .background(Color.blue)
                        .cornerRadius(15)
                }
                Button(action: { color = .red }) {
                    Text("Red")
                        .font(.title)
                        .bold()
                        .foregroundColor(.white)
                        .frame(width: 100)
                        .padding()
                        .background(Color.red)
                        .cornerRadius(15)
                }
            }
            .offset(y: -50)
            .navigationTitle("My Navigation")
        }
    }
}

谢谢你提供的解决方案,但是在将新视图推入导航栈时,我使用该方法遇到了问题,因此我选择了另一种方法。 - calebm

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