You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS 16 Beta下UICollectionView拖动Item时崩溃问题排查

问题原因及解决方案

原因分析

  1. iOS 16 交互逻辑变更:苹果在iOS 16中调整了UICollectionView交互拖动时的布局无效化流程,在拖动的某些中间阶段(比如刚触发拖动尚未产生实际位置变化、系统内部预校验步骤),previousIndexPaths和targetIndexPaths会传入空数组,而非iOS 15及更早版本中始终传入的非空数组。你代码中直接使用previousIndexPaths.first!、targetIndexPaths.first!这种强制解包操作,空数组的first会返回nil,强制解包就会触发崩溃。
  2. 职责越界的代码逻辑:你在UICollectionViewLayout的invalidationContext方法内调用了collectionView?.dataSource?.collectionView?(moveItemAt:to:)来修改数据源,这本身就不符合UICollectionView的设计规范——布局类只应负责布局相关的无效化处理,数据移动的逻辑应该由UICollectionView的UICollectionViewDataSource代理方法(也就是你已经实现的collectionView(_:moveItemAt:to:))来处理。iOS 16的变更放大了这个不规范写法的风险。

修复方案

1. 移除布局类中修改数据源的代码

把invalidationContext(forInteractivelyMovingItems:)里调用moveItemAt:to:的逻辑删掉,让UICollectionView通过代理方法自动触发数据移动:

open override func invalidationContext(forInteractivelyMovingItems targetIndexPaths: [IndexPath], withTargetPosition targetPosition: CGPoint, previousIndexPaths: [IndexPath], previousPosition: CGPoint) -> UICollectionViewLayoutInvalidationContext {
    let context = super.invalidationContext(forInteractivelyMovingItems: targetIndexPaths, withTargetPosition: targetPosition, previousIndexPaths: previousIndexPaths, previousPosition: previousPosition)
    
    // 移除这里修改数据源的代码,交给UICollectionView的dataSource代理处理
    return context
}

2. 给强制解包操作加安全校验

如果确实需要在布局方法中处理位置变化,必须先校验数组非空,避免强制解包崩溃:

open override func invalidationContext(forInteractivelyMovingItems targetIndexPaths: [IndexPath], withTargetPosition targetPosition: CGPoint, previousIndexPaths: [IndexPath], previousPosition: CGPoint) -> UICollectionViewLayoutInvalidationContext {
    let context = super.invalidationContext(forInteractivelyMovingItems: targetIndexPaths, withTargetPosition: targetPosition, previousIndexPaths: previousIndexPaths, previousPosition: previousPosition)
    
    // 先校验数组非空,再做后续逻辑
    guard let prevIndexPath = previousIndexPaths.first, 
          let targetIndexPath = targetIndexPaths.first,
          prevIndexPath.item != targetIndexPath.item else {
        return context
    }
    
    // 这里如果还有布局相关的逻辑,再执行,不要修改数据源
    return context
}

3. 优化数据源代理方法的逻辑

确保collectionView(_:moveItemAt:to:)只在拖动过程中处理数据,避免不必要的reloadData:

func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
    // 只有当长按手势未结束时才处理数据移动
    guard longPressGesture.state != .ended else {
        return
    }
    
    let movedItem = availableItems.remove(at: sourceIndexPath.item)
    availableItems.insert(movedItem, at: destinationIndexPath.item)
    
    // 不需要调用reloadData,UICollectionView会自动更新布局
}

内容的提问来源于stack exchange,提问作者smartsanja

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 09:24:43