onReceive(self.timer) 在 NavigationView 中不起作用

onReceive(self.timer) doesn't work inside a NavigationView

我有这个 Scrollview 的页面,其中包含一个自定义 header,只有当它滚动到一定高度时才会显示。我使用 GeometryReaderonReceive 来不断检查当前的滚动高度:

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

滚动和 header hiding/showing 完美运行,直到我将 ZStack 包裹在 NavigationView 中。 onReceive 根本就不再触发了。如果我将 NavigationViewZStack 交换,一切都会再次按预期工作。

我看过这个 问题,但我没有条件成分。这是 SwiftUI 错误还是我做错了什么?

这是针对您的案例的可能解决方案的演示。使用 Xcode 11.4 / iOS 13.4 测试(向前兼容)

我们的想法不是通过计时器而是通过视图位置变化做出反应,这已经 read/tracked 通过视图首选项。

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: -[=10=].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 = [=10=] > 200    // << your condition
        }
    }
}