如何让ScrollView的内容滚动,以与另一个视图的底边对齐?
var body: some View {
ZStack(alignment: .top) {
dateAndTime
ScrollView {
VStack {
storyContent
}
}
}
在向下滚动时,如何让VStack的顶部边缘与dateAndTime标签的底部边缘对齐,而不是回弹回ScrollView的顶部?
起初,我希望VStack能隐藏dateAndTime,但我希望用户能够滚动并在停止滚动时显示dateAndTime标签,同时保持两者边缘对齐,不把滚动位置重置到顶部。
我尝试将dateAndTime放在ScrollView的背景中,并使用.scrollTargetLayout与 .scrollTargetBehavior,但似乎不起作用。我猜这是因为dateAndTime标签在ScrollView之外。我不确定如何使用scroll targets或锚点。

解决方案
我建议在视图被向下拉时,简单地在 VStack 顶部加一点内边距。这样就能让dateAndTime标签在 VStack 的背后可见。说明如下:
.onScrollGeometryChange可以用来检测视图被向下拉动的时机。- 你也可能想要检查滚动阶段,这样只有在用户从静止状态与滚动视图交互时才显示头部,而不是在滚动大量内容重新进入视图时才显示。为此,可以使用
.onScrollPhaseChange。 - 有一个版本的
.onScrollPhaseChange可以把ScrollPhaseChangeContext提供给闭包。这也使得在阶段变化时检查滚动偏移成为可能。然而,我发现它只在阶段变化的瞬间报告偏移量,而不是持续地。因此,.onScrollGeometryChange也可能是必要的。 - 如果你想在视图没有被向下拉时对dateAndTime进行遮罩,那么背景可能需要应用到
VStack。 - 如果内容较短(如你的示例),通过在底部添加大量负的底边距,背景就能延伸到屏幕底部。或者,如果已知屏幕的几何尺寸,可以计算背景的高度,但负边距也许更简单且更合适。
下面是一个更详细的示例,演示它全部工作:
struct ContentView: View {
let headerHeight: CGFloat = 50
let scrollThreshold: CGFloat = 10
@State private var isInteractingFromRest = false
@State private var isHeaderShowing = false
private var dateAndTime: some View {
// ...
}
private var storyContent: some View {
// ...
}
var body: some View {
NavigationStack {
ZStack(alignment: .top) {
dateAndTime
ScrollView {
VStack {
storyContent
}
.frame(maxWidth: .infinity, alignment: .leading)
.background {
Color.blue
.padding(.bottom, -2000)
}
.padding(.top, isHeaderShowing ? headerHeight : 0)
}
.onScrollPhaseChange { _, newPhase, context in
if newPhase == .interacting {
let geo = context.geometry
let scrollOffset = geo.contentOffset.y + geo.contentInsets.top
isInteractingFromRest = abs(scrollOffset) < 1
} else {
isInteractingFromRest = false
}
}
.onScrollGeometryChange(for: Bool.self) { geo in
let result: Bool
if isInteractingFromRest {
let scrollOffset = geo.contentOffset.y + geo.contentInsets.top
result = scrollOffset < (isHeaderShowing ? scrollThreshold : -scrollThreshold)
} else {
result = isHeaderShowing
}
return result
} action: { _, newVal in
if newVal != isHeaderShowing {
withAnimation {
isHeaderShowing = newVal
}
}
}
.toolbar {
ToolbarItemGroup(placement: .topBarTrailing) {
Button("Edit") {}
Button("Delete", systemImage: "trash") {}
}
}
}
.background(.red.opacity(0.5))
}
}
}

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。