SwiftUI 实现上滑滚动时文本移入头部固定同行效果问询
解决方案
核心调整说明
- 拆分文本布局:将固定显示的
Text("Hello")放在ScrollView外层,保证滚动全程固定在头部位置 - 同步滚动状态:将滚动偏移量的监听结果直接绑定到
isHStacked状态,触发两个文本的布局切换 - 新增动画过渡:布局切换时添加系统默认动画,避免交互生硬跳变
- 修复语法问题:给未初始化的状态变量添加默认值,解决编译报错
完整实现代码
首先是无需修改的监听偏移量的PreferenceKey:
struct ViewOffsetKey: PreferenceKey { typealias Value = CGFloat static var defaultValue = CGFloat.zero static func reduce(value: inout Value, nextValue: () -> Value) { value += nextValue() } }
调整后的TagLineView,适配两种布局状态:
struct TagLineView: View { @Binding var isHStacked: Bool var body: some View { if isHStacked { // 滚动到阈值后,只显示Welcome和外部的Hello同行 Text("Welcome!") .font(.title) } else { // 初始状态,显示Hello+换行+Welcome VStack(alignment: .leading, spacing: 4) { Text("Hello") .font(.title) .fontWeight(.bold) Text("Welcome!") .font(.title) } } } }
调整后的主视图ContentView,外层固定Hello,内部监听滚动切换状态:
struct ContentView: View { // 给状态变量添加默认初始值 @State var isHStacked: Bool = false var body: some View { NavigationView { VStack(spacing: 0) { // 外层固定的头部区域,Hello始终固定在这里 HStack { Text("Hello") .font(.title) .fontWeight(.bold) // 滚动到阈值后显示Welcome,和Hello同行 if isHStacked { Text("Welcome!") .font(.title) } Spacer() } .padding(.horizontal) .padding(.vertical, 12) ScrollView { VStack(alignment:.leading, spacing: 0) { // 内部的标题区域,初始状态显示完整两行标题 TagLineView(isHStacked: $isHStacked) .padding() .opacity(isHStacked ? 0 : 1) // 切换后隐藏内部标题 ZStack(alignment:.leading) { Color(red: 0.937, green: 0.937, blue: 0.937) .edgesIgnoringSafeArea(.all) .cornerRadius(30.0) VStack(alignment: .leading, spacing: 20) { Text("Popular") .font(.custom("PlayfairDisplay-Bold", size: 24)) .padding(.leading,27) .padding(.top,20) ScrollView (.horizontal, showsIndicators: false) { HStack (spacing: 0) { ForEach(0 ..< 4) { i in ProductCardView(image: Image("chair_\(4-i)"), size: 180) } .padding(.leading) } } .padding(.bottom) Text("Best") .font(.custom("PlayfairDisplay-Bold", size: 24)) .padding(.horizontal) ScrollView (.horizontal, showsIndicators: false) { HStack (spacing: 0) { ForEach(0 ..< 4) { i in ProductCardView(image: Image("chair_\(4-i)"), size: 180) } .padding(.leading) } } Text("Best") .font(.custom("PlayfairDisplay-Bold", size: 24)) .padding(.horizontal) ScrollView (.horizontal, showsIndicators: false) { HStack (spacing: 0) { ForEach(0 ..< 4) { i in ProductCardView(image: Image("chair_\(4-i)"), size: 180) } .padding(.leading) } } .padding(.bottom, 30) } } .background(GeometryReader { Color.clear.preference(key: ViewOffsetKey.self, value: -$0.frame(in: .named("scroll")).origin.y) }) .onPreferenceChange(ViewOffsetKey.self) { offset in // 滚动偏移超过阈值时切换状态,加动画过渡 withAnimation { isHStacked = offset > 5 } } } } .coordinateSpace(name: "scroll") } .navigationBarHidden(true) } } }
补充ProductCardView占位实现,可直接运行测试:
struct ProductCardView: View { let image: Image let size: CGFloat var body: some View { RoundedRectangle(cornerRadius: 16) .fill(Color.gray.opacity(0.3)) .frame(width: size, height: size) .padding(.trailing, 16) } }
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

