I have this page of a Scrollview
with a custom header only shown when it scrolls past a certain height. I use GeometryReader
with onReceive
to constantly check the current scrolling height:
@State var userInfoUpateInterval = Timer.publish(every: 0.1, on: .current, in: .tracking).autoconnect()
@State var showHeader: Bool = false
var body: some View {
NavigationView {
ZStack(alignment: .top) {
ScrollView(.vertical) {
GeometryReader { geometry in
Text("User info component").onReceive(self.userInfoUpateInterval) { (_) in
self.onUserInfoLayoutChange(geometry)
}
}
VStack {
Text("content")
}.frame(width: UIScreen.screenWidth, height: 1500)
}
ProfileHeader(title: "user.userName", showHeader: $showHeader)
}
}
}
The scrolling and header hiding/showing works perfectly until I wrapped the ZStack
in a NavigationView
. onReceive
is simply not triggered anymore. If I swap NavigationView
with a ZStack
everything works as expected again.
I have seen this Timer onReceive not working inside NavigationView question but I don't have conditional component. Is this a SwiftUI bug or I'm doing something wrong?
Here is a demo of possible solution for your case. Tested with Xcode 11.4 / iOS 13.4 (and it is forward compatible)
The idea is to react not by timer but by view position change that has been read/tracked by view preferences.
struct ViewOffsetKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
}
}
struct DemoView: View {
@State var showHeader: Bool = false
var body: some View {
NavigationView {
ZStack(alignment: .top) {
ScrollView(.vertical) {
Text("User info component")
.background(GeometryReader {
Color.clear.preference(key: ViewOffsetKey.self,
value: -$0.frame(in: .named("scroll_area")).origin.y) })
VStack {
Text("content")
}.frame(maxWidth: .infinity, minHeight: 1500)
}.coordinateSpace(name: "scroll_area")
if showHeader {
Text("ProfileHeader")
}
}
}
.onPreferenceChange(ViewOffsetKey.self) {
self.showHeader = $0 > 200 // << your condition
}
}
}