SwiftUI横向ScrollView增删Item时如何保持X轴滚动位置?
SwiftUI横向ScrollView增删Item后保持滚动位置的解决方案
问题核心
增删Item时,ScrollView因内容结构变更会自动重置滚动偏移,需手动保存并恢复偏移量,同时根据内容宽度变化调整偏移值,维持视觉上的滚动位置不变。
解决步骤
1. 保存当前滚动偏移
在ItemView中添加状态变量存储实时滚动偏移,修改滚动回调方法保存偏移:
public struct ItemView: View { var action: () -> Void @ObservedObject var itemData: ItemData @State private var isReducedSize: Bool = false @State private var currentOffset = CGPoint.zero // 新增:保存当前滚动偏移 public init(action: @escaping () -> Void, itemData: ItemData) { self.action = action self.itemData = itemData } private func handleScroll(_ offset: CGPoint) { currentOffset = offset // 实时保存偏移 withAnimation { isReducedSize = offset.x >= -100 } } }
2. 修改ScrollViewWithOffset支持偏移控制
针对自定义的ScrollViewWithOffset,添加绑定变量控制滚动偏移,确保内容更新后自动恢复偏移:
struct ScrollViewWithOffset: UIViewRepresentable { var content: () -> UIView var onScroll: (CGPoint) -> Void @Binding var scrollOffset: CGPoint // 新增:绑定外部偏移量 func makeUIView(context: Context) -> UIScrollView { let scrollView = UIScrollView() scrollView.showsHorizontalScrollIndicator = false scrollView.showsVerticalScrollIndicator = false let contentView = content() scrollView.addSubview(contentView) contentView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ contentView.topAnchor.constraint(equalTo: scrollView.topAnchor), contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor), contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), contentView.heightAnchor.constraint(equalTo: scrollView.heightAnchor) ]) scrollView.delegate = context.coordinator return scrollView } func updateUIView(_ uiView: UIScrollView, context: Context) { // 内容更新后恢复之前的滚动偏移 if uiView.contentOffset != scrollOffset { uiView.contentOffset = scrollOffset } } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, UIScrollViewDelegate { var parent: ScrollViewWithOffset init(_ parent: ScrollViewWithOffset) { self.parent = parent } func scrollViewDidScroll(_ scrollView: UIScrollView) { parent.onScroll(scrollView.contentOffset) } } }
3. 绑定偏移并调整增删后的偏移量
在ItemView中关联偏移绑定,同时监听Item增删事件,根据内容宽度变化调整偏移:
public var body: some View { HStack { ScrollViewWithOffset( content: { UIHostingController(rootView: ItemCollectionView(itemData: itemData)).view }, onScroll: handleScroll, scrollOffset: $currentOffset ) } .padding(8) .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("AdjustScrollOffset"))) { notification in if let widthDelta = notification.object as? CGFloat { currentOffset.x += widthDelta } } }
4. 在ItemData中触发偏移调整通知
修改ItemData的增删方法,根据Item宽度发送偏移调整通知:
class ItemData: ObservableObject { @Published var items: [Item] = [] let itemWidth: CGFloat = 80 // 根据实际Item宽度修改 func add(_ item: Item) { items.append(item) // 添加Item后内容宽度增加,偏移需同步调整 NotificationCenter.default.post(name: NSNotification.Name("AdjustScrollOffset"), object: itemWidth) } func remove(_ item: Item) { guard let index = items.firstIndex(where: { $0.id == item.id }) else { return } items.remove(at: index) // 删除Item后内容宽度减少,偏移需同步调整 NotificationCenter.default.post(name: NSNotification.Name("AdjustScrollOffset"), object: -itemWidth) } }
iOS 17+简化方案
若项目适配iOS 17及以上,可直接使用官方scrollPosition修饰符,无需自定义ScrollView:
ScrollView(.horizontal) { HStack { ForEach(itemData.items, id: \.id) { item in itemView( content: itemViewContentProvider.content( item: item, onitemTapped: { item in itemData.add(item) }, onDelete: { item in itemData.remove(item) } ) ) } } .scrollTargetLayout() } .scrollPosition(id: $lastSelectedItemId) // 绑定最后选中的Item ID,增删后保持指向同一Item
内容的提问来源于stack exchange,提问作者user19344300
相关产品推荐
相关产品推荐

