iOS16+ SwiftUI TabView滚动越界崩溃问题排查
问题现象
进入页面后App立即崩溃,报错信息如下:
*** Assertion failure in -[_TtC7SwiftUIP33_8825076C2763A50452A210CBE1FA4AF020PagingCollectionView _validateScrollingTargetIndexPath:], UICollectionView.m:7339
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Attempted to scroll the collection view to an out-of-bounds item (0) when there are only 0 items in section 0...'
场景说明:
- 未直接使用UICollectionView,采用SwiftUI的TabView配合ViewModel动态获取API数据
- iOS 16以下设备运行正常
- 将ForEach循环改为固定数量(如0..<12)时也可正常运行
问题原因
iOS 16+版本中,SwiftUI的PageTabViewStyle内部基于UICollectionView实现。当TabView初始化时数据源为空(即myDataVM.data为空,分块后data.count为0),内部的UICollectionView会尝试滚动到索引0的位置,但此时没有任何数据项,触发了系统的断言检查,导致崩溃。而iOS 16之前的版本对这种场景有容错处理,因此不会崩溃。
解决方案
通过条件渲染确保TabView仅在数据加载完成后才被渲染,避免初始空数据状态下触发内部断言。修改后的代码如下:
struct DataDisplayView: View { @StateObject private var myDataVM = MyDataViewModel() var body: some View { GeometryReader { reader in // 仅当数据非空时渲染TabView if !myDataVM.data.isEmpty { TabView { let data = myDataVM.data.chunked(into: 3) ForEach(0..<data.count, id: \.self) { index in Circle() .fill(Color.orange.opacity(0.3)) .frame(width: 100, height: 100) } .rotationEffect(.degrees(-90)) .frame( width: reader.size.width, height: reader.size.height ) } .frame( width: reader.size.height, height: reader.size.width ) .rotationEffect(.degrees(90), anchor: .topLeading) .offset(x: reader.size.width) .tabViewStyle( PageTabViewStyle(indexDisplayMode: .never) ) } else { // 数据加载中的占位视图,可根据需求替换 ProgressView() .foregroundColor(.white) } } .background(Color.black) .onAppear() { myDataVM.dataWebServiceCall() } } }
方案说明
- 通过
if !myDataVM.data.isEmpty判断数据状态,确保TabView只在有数据时渲染 - 空数据状态下显示加载占位视图(如
ProgressView),提升用户体验 - 彻底避免了TabView在空数据源时触发内部UICollectionView的滚动断言
其他可选方案
如果需要TabView始终存在,也可以初始化时给myDataVM.data设置一个最小的占位数据(比如一个空元素的数组),确保分块后data.count至少为1,但这种方案不如条件渲染直观,且可能引入不必要的视图渲染。
内容的提问来源于stack exchange,提问作者Bhanuteja

