Dynamically Changing Navigation Bar Color in SwiftUI and iOS 15


I’m seeking a method to dynamically change the navigation bar color in SwiftUI on iOS 15.

I’d like the navigation bar color to dynamically change based on the offset of a ScrollView. However, despite correctly altering the ‘color’ variable when using UINavigationBarAppearance, the view itself does not seem to update.

Is there a solution for this issue? Unfortunately, the minimum supported version is iOS 15.

Here’s the code I’ve written:

import SwiftUI

struct ScrollOffset: View {
    private enum Metrics {
        static let statusBarRect: CGRect = UIApplication.shared.windows.filter(\.isKeyWindow).first?.windowScene?.statusBarManager?.statusBarFrame ?? .zero
        static let defaultNavigationBarHeight: CGFloat = 44
    }
    
    @State private var scrollOffset: CGFloat = 0.0
    let navigationBarHeight: CGFloat = 44.0 // 네비게이션 바의 높이
    let maxScrollOffset: CGFloat = 100.0
    
    @State private var navigationBarColor: Color = .clear
    
    init() { }
    
    var body: some View {
        GeometryReader { geometry in
            ScrollView {
                LazyVStack {
                    Text("Text1")
                        .frame(height: 100)
                    Text("Text1")
                        .frame(height: 100)
                    Text("Text1")
                        .frame(height: 100)
                    Text("Text1")
                        .frame(height: 100)
                    Text("Text1")
                        .frame(height: 100)
                    Text("Text1")
                        .frame(height: 100)
                    Text("Text1")
                        .frame(height: 100)
                    Text("Text1")
                        .frame(height: 100)
                    Text("Text1")
                        .frame(height: 100)
                    Text("Text1")
                        .frame(height: 100)
                }
                .scrollViewOffset { point in
                    let scrollOffset = -(point.y - Metrics.statusBarRect.height - Metrics.defaultNavigationBarHeight)
                    let navigationBarOpacity: Double
                    if scrollOffset >= Metrics.defaultNavigationBarHeight {
                        navigationBarOpacity = 1
                    } else {
                        let opacity = scrollOffset / Metrics.defaultNavigationBarHeight
                        navigationBarOpacity = min(max(opacity, 0), 1)
                    }
                    
                    print("@@ navigationBarOpacity: ", navigationBarOpacity)
                    navigationBarColor = Color.red.opacity(navigationBarOpacity)
                    
                    
//                    if navigationBarOpacity < 0.5 {
//                        color = .red
//                    } else {
//                        color = .green
//                    }
//                    print("@@ navigationBarOpacity: ", navigationBarOpacity)
//                    navigationBarColor = color
                }
                
                .navigationBarColor($navigationBarColor)
            }
            let _ = print("@@ self.scrollOffset: ", self.scrollOffset)
        }
    }
}

struct OffsetPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
    
    typealias Value = CGFloat
}

struct NavigationBarViewModifier: ViewModifier {
    @Binding private var color: Color
    init(color: Binding<Color>) {
        self._color = color
    }

    func body(content: Content) -> some View {
        content
            .onChange(of: color) { color in
                let navigationAppearance = UINavigationBarAppearance()
                navigationAppearance.configureWithTransparentBackground()
                navigationAppearance.backgroundColor = UIColor(color)
                print("@@ navigationBarColor: ", color)
        
                UINavigationBar.appearance().standardAppearance = navigationAppearance
                UINavigationBar.appearance().compactAppearance = navigationAppearance
                UINavigationBar.appearance().scrollEdgeAppearance = navigationAppearance
            }
    }
}

extension View {
    func navigationBarColor(_ color: Binding<Color>) -> some View {
        return modifier(NavigationBarViewModifier(color: color))
    }
    
    func scrollViewOffset(_ perform: @escaping (CGPoint) -> Void) -> some View {
        coordinateSpace(name: "ScrollView")
            .background(
                GeometryReader { geometryProxy in
                    Color.clear
                        .preference(key: ScrollViewOffsetPreferenceKey.self, value: geometryProxy.frame(in: .named("ScrollView")).origin)
                }
            )
            .onPreferenceChange(ScrollViewOffsetPreferenceKey.self, perform: perform)
    }
    
    func scrollViewOffset(_ binding: Binding<CGPoint>) -> some View {
        scrollViewOffset { point in
            binding.wrappedValue = point
        }
    }
}

struct ScrollViewOffsetPreferenceKey: PreferenceKey {
    static var defaultValue: CGPoint { .zero }
    
    static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) {
        value.x += nextValue().x
        value.y += nextValue().y
    }
}

simulator

enter image description here

console

enter image description here

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img