我使用
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。
还有其他方法可以实现这一目标吗?
您可以使用我在
这个答案中编写的这个
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
}
}
}
}