☰
swiftui-expert-skill - scroll-patterns
2026/10/14 3:06:10 网站建设 项目流程

SwiftUI ScrollView 模式参考

目录

  • 用于编程式滚动的 ScrollViewReader
  • 滚动位置跟踪
  • 滚动过渡与效果
  • 滚动目标行为
  • 汇总清单

用于编程式滚动的 ScrollViewReader

使用ScrollViewReader实现滚动到顶部、滚动到底部和基于锚点的跳转。

structChatView:View{@Stateprivatevarmessages:[Message]=[]privateletbottomID="bottom"varbody:someView{ScrollViewReader{proxyinScrollView{LazyVStack{ForEach(messages){messageinMessageRow(message:message).id(message.id)}Color.clear.frame(height:1).id(bottomID)}}.onChange(of:messages.count){_,_inwithAnimation{proxy.scrollTo(bottomID,anchor:.bottom)}}.onAppear{proxy.scrollTo(bottomID,anchor:.bottom)}}}}

滚动到顶部模式

structFeedView:View{@Stateprivatevaritems:[Item]=[]@StateprivatevarscrollToTop=falseprivatelettopID="top"varbody:someView{ScrollViewReader{proxyinScrollView{LazyVStack{Color.clear.frame(height:1).id(topID)ForEach(items){iteminItemRow(item:item)}}}.onChange(of:scrollToTop){_,shouldScrollinifshouldScroll{withAnimation{proxy.scrollTo(topID,anchor:.top)}scrollToTop=false}}}}}

原因:ScrollViewReader提供带稳定锚点的编程式滚动控制。始终使用稳定的 ID 和显式动画。

滚动位置跟踪

基本滚动位置

避免- 直接存储滚动位置会在每个滚动帧触发视图更新:

// ❌ 坏实践 - 导致不必要的重新渲染structContentView:View{@StateprivatevarscrollPosition:CGFloat=0varbody:someView{ScrollView{content.background(GeometryReader{geometryinColor.clear.preference(key:ScrollOffsetPreferenceKey.self,value:geometry.frame(in:.named("scroll")).minY)})}.coordinateSpace(name:"scroll").onPreferenceChange(ScrollOffsetPreferenceKey.self){valueinscrollPosition=value}}}

推荐- 检查滚动位置并根据阈值更新标志,以获得更平滑、更高效的滚动:

// ✅ 好实践 - 仅在跨越阈值时更新状态structContentView:View{@StateprivatevarstartAnimation:Bool=falsevarbody:someView{ScrollView{content.background(GeometryReader{geometryinColor.clear.preference(key:ScrollOffsetPreferenceKey.self,value:geometry.frame(in:.named("scroll")).minY)})}.coordinateSpace(name:"scroll").onPreferenceChange(ScrollOffsetPreferenceKey.self){valueinifvalue<-100{startAnimation=true}else{startAnimation=false}}}}structScrollOffsetPreferenceKey:PreferenceKey{staticvardefaultValue:CGFloat=0staticfuncreduce(value:inoutCGFloat,nextValue:()->CGFloat){value=nextValue()}}

基于滚动的头部可见性

structContentView:View{@StateprivatevarshowHeader=truevarbody:someView{VStack(spacing:0){ifshowHeader{HeaderView().transition(.move(edge:.top))}ScrollView{content.background(GeometryReader{geometryinColor.clear.preference(key:ScrollOffsetPreferenceKey.self,value:geometry.frame(in:.named("scroll")).minY)})}.coordinateSpace(name:"scroll").onPreferenceChange(ScrollOffsetPreferenceKey.self){offsetinifoffset<-50{// 向下滚动withAnimation{showHeader=false}}elseifoffset>50{// 向上滚动withAnimation{showHeader=true}}}}}}

滚动过渡与效果

iOS 17+:本节中的所有 API 需要 iOS 17 或更高版本。

基于滚动的不透明度

structParallaxView:View{varbody:someView{ScrollView{LazyVStack(spacing:20){ForEach(items){iteminItemCard(item:item).visualEffect{content,geometryinletframe=geometry.frame(in:.scrollView)letdistance=min(0,frame.minY)returncontent.opacity(1+distance/200)}}}}}}

视差效果

structParallaxHeader:View{varbody:someView{ScrollView{VStack(spacing:0){Image("hero").resizable().aspectRatio(contentMode:.fill).frame(height:300).visualEffect{content,geometryinletoffset=geometry.frame(in:.scrollView).minYreturncontent.offset(y:offset>0?-offset*0.5:0)}.clipped()ContentView()}}}}

滚动目标行为

iOS 17+:本节中的所有 API 需要 iOS 17 或更高版本。

分页 ScrollView

structPagingView:View{varbody:someView{ScrollView(.horizontal){LazyHStack(spacing:0){ForEach(pages){pageinPageView(page:page).containerRelativeFrame(.horizontal)}}.scrollTargetLayout()}.scrollTargetBehavior(.paging)}}

吸附到项目

structSnapScrollView:View{varbody:someView{ScrollView(.horizontal){LazyHStack(spacing:16){ForEach(items){iteminItemCard(item:item).frame(width:280)}}.scrollTargetLayout()}.scrollTargetBehavior(.viewAligned).contentMargins(.horizontal,20)}}

汇总清单

  • 使用带稳定 ID 的ScrollViewReader进行编程式滚动
  • 始终使用显式动画配合scrollTo()
  • 基于滚动的视觉变化使用.visualEffect
  • 分页行为使用.scrollTargetBehavior(.paging)
  • 吸附到项目行为使用.scrollTargetBehavior(.viewAligned)
  • 用阈值门控频繁的滚动位置更新
  • 自定义滚动位置跟踪使用偏好键

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询