onReceive (self.timer) no funciona dentro de un NavigationView

Aug 25 2020

Tengo esta página Scrollviewcon un encabezado personalizado que solo se muestra cuando se desplaza más allá de una cierta altura. Utilizo GeometryReadercon onReceivepara comprobar constantemente la altura de desplazamiento actual:

@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)
        }
    }
}

El desplazamiento y la ocultación / visualización del encabezado funcionan perfectamente hasta que envuelvo el ZStacken un NavigationView. onReceivesimplemente ya no se activa. Si cambio NavigationViewcon un, ZStacktodo vuelve a funcionar como se esperaba.

He visto que este Timer onReceive no funciona dentro de la pregunta de NavigationView pero no tengo un componente condicional. ¿Es esto un error de SwiftUI o estoy haciendo algo mal?

Respuestas

1 Asperi Aug 28 2020 at 14:57

Aquí hay una demostración de una posible solución para su caso. Probado con Xcode 11.4 / iOS 13.4 (y es compatible con versiones posteriores)

La idea es reaccionar no por el temporizador sino por el cambio de posición de la vista que ha sido leído / seguido por las preferencias de vista.

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
        }
    }
}