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
console






