检测用户何时滚动到 SwiftUI 中水平 ScrollView 的末尾

问题描述 投票:0回答:1

我使用

ScrollView
HStack
创建了一个水平轮播。

这是我的代码:

struct CarouselView<Content: View>: View {
    let content: Content
    
    private let spacing: CGFloat
    private let shouldSnap: Bool
    
    init(spacing: CGFloat = .zero,
         shouldSnap: Bool = false,
         @ViewBuilder content: @escaping () -> Content) {
        self.content = content()
        self.spacing = spacing
        self.shouldSnap = shouldSnap
    }
    
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: spacing) {
                content
            }.apply {
                if #available(iOS 17.0, *), shouldSnap {
                    $0.scrollTargetLayout()
                } else {
                    $0
                }
            }
        }
        .clipped()
        .apply {
            if #available(iOS 17.0, *), shouldSnap {
                $0.scrollTargetBehavior(.viewAligned)
            } else {
                $0
            }
        }
    }
}

然后我可以按如下方式使用它:

CarouselView(spacing: 10) {
    ForEach(0 ..< imagesNames.count, id: \.self) { index in
        DemoView()
    }
}

如何检测用户何时滚动到滚动视图的末尾。

我已经尝试了一些答案here - 在hstack末尾添加视图并在该视图的

onAppear
上进行工作的主要方法不起作用,因为它在创建HStack时立即被触发.

如果我使用

LazyHStack
,这可能会起作用 - 但是,由于一些限制,我必须使用 HStack。

还有其他方法可以实现这一目标吗?

ios swift swiftui hstack swiftui-scrollview
1个回答
0
投票

您可以使用我在

这个答案
中编写的这个DetectOnScreen视图修饰符来检测屏幕上是否存在不可见视图。

struct IsOnScreenKey: PreferenceKey {
    
    static let defaultValue: Bool? = nil
    static func reduce(value: inout Value, nextValue: () -> Value) {
        if let next = nextValue() {
            value = next
        }
    }
}

struct DetectIsOnScreen: ViewModifier {
    func body(content: Content) -> some View {
        GeometryReader { reader in
            content
                .preference(
                    key: IsOnScreenKey.self,
                    // bounds(of: .scrollView) gives us the scroll view's frame
                    // frame(in: .local) gives us the frame of the invisible view
                    // both are in the local coordinate space
                    value: reader.bounds(of: .scrollView)?.intersects(reader.frame(in: .local)) ?? false
                )
        }
    }
}

用途:

ScrollView(.horizontal, showsIndicators: false) {
    HStack(spacing: spacing) {
        content
        Color.clear
            .frame(width: 0, height: 0)
            .modifier(DetectIsOnScreen())
            .onPreferenceChange(IsOnScreenKey.self) { value in
                if value == true {
                    print("Has reached end!")
                }
            }
    }
}

这确实意味着

HStack
将具有额外的视图,因此最后一个视图和不可见视图之间会有一些额外的空间(大小为
spacing
)。如果这是不可取的,您可以将最后一个视图包装在其自己的
HStack
中,并将不可见视图放在内部
HStack
中。但这需要在使用现场完成。

ForEach(0..<imagesNames.count, id: \.self) { index in
    if index == imageNames.count - 1 {
        HStack(spacing: 0) {
            DemoView()
            Color.clear
                .frame(width: 0, height: 0)
                ...
        }
    } else { DemoView() }
}

如果您不介意使用View Extractor,您可以在

CarouselView

中执行此操作
HStack(spacing: spacing) {
    ExtractMulti(content) { views in
        ForEach(views) { view in
            if view.id == views.last?.id {
                HStack(spacing: 0) {
                    view
                    Color.clear
                        .frame(width: 0, height: 0)
                        ...
                }
            } else {
                view
            }
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.