我正在开发一款应用程序,在其余“探索”内容之前,我会先呈现一个或多个特色全屏视图(想想 Spotify 的主页)。
我目前有以下结构:
GeometryReader { proxy in
let h = proxy.size.height
ScrollView {
if data == nil {
Recommended(event: nil)
.padding(.vertical, 8)
.frame(height: h)
.frame(maxWidth: .infinity)
} else {
LazyVStack(spacing: proxy.safeAreaInsets.bottom / 2) {
ForEach(data?.recommended ?? []) { _ in
Recommended(event: events[0])
.padding(.vertical, 8)
.frame(height: h)
.frame(maxWidth: .infinity)
.padding(.bottom, proxy.safeAreaInsets.bottom / 2)
}
}
.scrollTargetLayout()
}
if let sections = data?.sections {
LazyVStack(spacing: 32) {
ForEach(Array(sections.enumerated()), id: \.offset) { e in
let section = e.element
EventRow(title: section.title, events: section.events)
}
}
.scrollTargetLayout()
}
Spacer()
}
// .scrollPosition(id: $scrollID) this was an attempt
// .scrollTargetBehavior(.custom) this was another attempt
.scrollDisabled(data == nil)
.scrollIndicators(.hidden)
.ignoresSafeArea(.container)
.refreshable {
print("refresh")
}
.onChange(of: scrollID) { _, newValue in
print(newValue ?? "")
}
}
我尝试根据
scrollID
切换分页样式,但在编写三元后得到以下结果
.scrollTargetBehavior(true ? .paging : .viewAligned)
// Member 'viewAligned' in 'PagingScrollTargetBehavior' produces result of type 'ViewAlignedScrollTargetBehavior', but context expects 'PagingScrollTargetBehavior'
我也在研究滚动目标行为的自定义实现,但我的实验没有取得什么结果。
最小可重现示例
struct ContentView: View {
let colors: [Color] = [.blue, .green, .yellow, .red]
var body: some View {
GeometryReader { proxy in
ScrollView(.vertical) {
LazyVStack(spacing: proxy.safeAreaInsets.bottom / 2) {
ForEach(0 ..< 2) { i in
ZStack {
Rectangle()
.fill(colors[i % colors.count].opacity(0.6))
.containerRelativeFrame([.horizontal, .vertical])
.padding(.bottom, proxy.safeAreaInsets.bottom / 2)
.frame(
width: proxy.size.width,
height: proxy.size.height + (proxy.safeAreaInsets.bottom / 2)
)
Text("Video \(i + 1)")
.font(.title)
.bold()
}
}
}
.scrollTargetLayout()
// these elements should scroll normally without snapping
ForEach(0 ..< 20) { i in
Rectangle()
.fill(.purple)
.frame(width: 100, height: 100)
.overlay {
Text("\(i)")
}
}
}
.scrollTargetBehavior(.paging)
.ignoresSafeArea(edges: .bottom)
}
}
}
#Preview {
ContentView()
}
您可以编写自己的
ScrollTargetBehaviour
。它的 updateTarget
将检查滚动视图自然滚动到的位置,如果它仍在“分页区域”中,则委托给 PagingScrollTargetBehavior
。否则它什么也不做,即自然滚动。
struct PartlyPagingBehaviour: ScrollTargetBehavior {
let pageCount: Int
func updateTarget(_ target: inout ScrollTarget, context: TargetContext) {
let targetDimension = context.axes == .vertical ? target.rect.maxY : target.rect.maxX
let containerDimension = context.axes == .vertical ? context.containerSize.height : context.containerSize.width
if targetDimension < containerDimension * CGFloat(pageCount) {
PagingScrollTargetBehavior.paging.updateTarget(&target, context: context)
}
}
}
通过检查
target.rect.maxY
,如果非分页区域的任何部分可见,则此实现将使用自然行为。如果您希望在自然行为开始之前分页区域完全不可见,您可以选中 target.rect.minY
。
对于 MRE 中的示例,您可以使用
.scrollTargetBehavior(PartlyPagingBehaviour(pageCount: 2))
也就是说,捕捉到每个页面比实际的
.paging
行为要慢一些。我认为 PagingScrollTargetBehavior
是 SwiftUI 检查的特殊情况,并且处理方式有点不同。