SwiftUI SnapCarousel无法吸附到图片问题排查求助
问题:SnapCarousel轮播组件向右滚动无法吸附到对应图片
我实现了SnapCarousel轮播组件,图片可正常显示,但向右滚动时无法吸附到对应图片,预期滚动后会自动吸附但未生效。
SnapCarousel.swift 代码
import SwiftUI struct SnapCarousel<Content : View,T: Identifiable>: View{ var content: (T) -> Content var list: [T] // Properties... var spacing: CGFloat var trailingSpace: CGFloat @Binding var index: Int init(spacing: CGFloat = 15,trailingSpace: CGFloat = 100,index: Binding<Int>,items: [T],@ViewBuilder content: @escaping (T)->Content) { self.list = items self.spacing = spacing self.trailingSpace = trailingSpace self._index = index self.content = content } //offset @GestureState var offset: CGFloat = 0 @State var currentIndex: Int = 0 var body: some View { GeometryReader{ proxy in let width = proxy.size.width - (trailingSpace - spacing) let adjustMentWidth = (trailingSpace / 2) - spacing HStack(spacing: spacing){ ForEach(list){item in content(item) .frame(width: proxy.size.width - trailingSpace) } .padding(.horizontal,spacing) .offset(x: (CGFloat(currentIndex) * -width) + (currentIndex != 0 ? adjustMentWidth : 0) + offset) .gesture( DragGesture() .updating($offset, body: { value, out, _ in out = value.translation.width }) .onEnded({ value in //Update current index for scroll let offsetX = value.translation.width //convert the translation into progress (0-1) //round the value based on the currentIndex let progress = -offsetX / width let roundIndex = progress.rounded() //setting max and min currentIndex = max(min(currentIndex + Int(roundIndex), list.count - 1), 0) //updating index currentIndex = index }) ) } } //animation when offset = 0 .animation(.easeInOut, value: offset == 0) } } struct SnapCarousel_Previews: PreviewProvider { static var previews: some View { MotorDetail(landmark: landmarks[0]) } }
MotorDetail视图调用代码
VStack (alignment: .leading, spacing: 12) {} .frame(maxWidth: .infinity,alignment: .leading) .padding() // Snap Carousel.... SnapCarousel (trailingSpace: 27,index: $currentIndex, items: posts) {post in GeometryReader{proxy in let size = proxy.size Image(post.postImage) .resizable() .aspectRatio(contentMode: .fill) .frame(width: size.width) .cornerRadius(12) .padding(.top, -200) } } .padding(.vertical,80) } .frame(maxHeight: .infinity, alignment: .top) .onAppear{ for index in 1...5{ posts.append(Post(postImage: "\(String(landmark.name))" + "_overview\(index)"))}
问题定位与修复方案
核心问题
- 索引赋值逻辑错误:在
DragGesture的onEnded回调中,最后一行currentIndex = index完全覆盖了之前计算好的滚动目标索引,导致滚动后的位置无法正确更新,这是吸附失效的关键原因。 - 动画触发条件不准确:原代码仅在
offset == 0时触发动画,无法覆盖滚动结束后索引更新的场景,导致吸附动画不生效。
修复后的SnapCarousel代码
import SwiftUI struct SnapCarousel<Content : View,T: Identifiable>: View{ var content: (T) -> Content var list: [T] var spacing: CGFloat var trailingSpace: CGFloat @Binding var index: Int init(spacing: CGFloat = 15,trailingSpace: CGFloat = 100,index: Binding<Int>,items: [T],@ViewBuilder content: @escaping (T)->Content) { self.list = items self.spacing = spacing self.trailingSpace = trailingSpace self._index = index self.content = content } @GestureState var offset: CGFloat = 0 @State var currentIndex: Int = 0 var body: some View { GeometryReader{ proxy in let width = proxy.size.width - (trailingSpace - spacing) let adjustMentWidth = (trailingSpace / 2) - spacing HStack(spacing: spacing){ ForEach(list){item in content(item) .frame(width: proxy.size.width - trailingSpace) } } .padding(.horizontal,spacing) .offset(x: (CGFloat(currentIndex) * -width) + (currentIndex != 0 ? adjustMentWidth : 0) + offset) .gesture( DragGesture() .updating($offset, body: { value, out, _ in out = value.translation.width }) .onEnded({ value in let offsetX = value.translation.width let progress = -offsetX / width let roundIndex = progress.rounded() // 更新内部维护的索引 currentIndex = max(min(currentIndex + Int(roundIndex), list.count - 1), 0) // 同步到外部绑定的索引值 index = currentIndex }) ) } // 监听索引和偏移量变化,确保滚动结束后触发吸附动画 .animation(.easeInOut, value: [currentIndex, offset]) } } struct SnapCarousel_Previews: PreviewProvider { static var previews: some View { MotorDetail(landmark: landmarks[0]) } }
额外注意事项
- 确保调用组件时,
currentIndex的初始值为0,避免初始位置偏移。 - 检查
posts数组在onAppear中的初始化逻辑,确保数组元素数量正确且图片资源名称无误。
内容的提问来源于stack exchange,提问作者robbe505
相关产品推荐
相关产品推荐

