onReceive (self.timer) не работает внутри NavigationView

Aug 25 2020

У меня есть эта страница Scrollviewс настраиваемым заголовком, который отображается только тогда, когда он прокручивается выше определенной высоты. Я использую GeometryReaderwith, onReceiveчтобы постоянно проверять текущую высоту прокрутки:

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

Прокрутка и скрытие / отображение заголовка работают отлично, пока я не завернул файл ZStackв файл NavigationView. onReceiveпросто больше не срабатывает. Если бы я поменяться NavigationViewс ZStackвсе работает как раз.

Я видел, что этот Timer onReceive не работает внутри вопроса NavigationView, но у меня нет условного компонента. Это ошибка SwiftUI или я что-то делаю не так?

Ответы

1 Asperi Aug 28 2020 at 14:57

Вот демонстрация возможного решения для вашего случая. Протестировано с Xcode 11.4 / iOS 13.4 (и совместимо с предыдущими версиями)

Идея состоит в том, чтобы реагировать не по таймеру, а по изменению положения просмотра, которое было прочитано / отслежено настройками просмотра.

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