onReceive (self.timer) non funziona all'interno di NavigationView

Aug 25 2020

Ho questa pagina di un Scrollviewcon un'intestazione personalizzata mostrata solo quando scorre oltre una certa altezza. Uso GeometryReadercon onReceiveper controllare costantemente l'altezza di scorrimento corrente:

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

Lo scorrimento e l'occultamento / visualizzazione dell'intestazione funzionano perfettamente finché non ho inserito il file ZStackin un file NavigationView. onReceivesemplicemente non viene più attivato. Se cambio NavigationViewcon un ZStacktutto funziona di nuovo come previsto.

Ho visto questo Timer onReceive non funzionare all'interno della domanda NavigationView ma non ho un componente condizionale. È un bug di SwiftUI o sto facendo qualcosa di sbagliato?

Risposte

1 Asperi Aug 28 2020 at 14:57

Ecco una demo della possibile soluzione per il tuo caso. Testato con Xcode 11.4 / iOS 13.4 (ed è compatibile con le versioni successive)

L'idea è di reagire non tramite il timer ma tramite il cambio di posizione della vista che è stato letto / monitorato dalle preferenze di visualizzazione.

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