onReceive (self.timer) não funciona dentro de um NavigationView
Eu tenho esta página de um Scrollviewcom um cabeçalho personalizado mostrado apenas quando rola além de uma certa altura. Eu uso GeometryReadercom onReceivepara verificar constantemente a altura de rolagem atual:
@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)
}
}
}
A rolagem e a ocultação / exibição do cabeçalho funcionam perfeitamente até que eu envolva o ZStackem a NavigationView. onReceivesimplesmente não é mais acionado. Se eu trocar NavigationViewpor um, ZStacktudo funcionará conforme o esperado novamente.
Eu vi este Timer onReceive não funcionar dentro da questão NavigationView, mas não tenho o componente condicional. É um bug do SwiftUI ou estou fazendo algo errado?
Respostas
Aqui está uma demonstração de uma possível solução para o seu caso. Testado com Xcode 11.4 / iOS 13.4 (e é compatível com versões futuras)
A ideia é reagir não por cronômetro, mas por mudança de posição de visualização que foi lida / rastreada pelas preferências de visualização.
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
}
}
}