TZImagePickerController icon indicating copy to clipboard operation
TZImagePickerController copied to clipboard

版本3.8.9 图片很多时候触发崩溃

Open CoderEYLee opened this issue 7 months ago • 6 comments

提bug前必看 请先回答下列三个问题,否则不允处理,谢谢配合。 1、我最新的Demo是否有这个bug?【如果Demo没问题,请升级新版】 答:不好复现

2、你用的是什么版本?升级到最新版后是否正常? 答: 3.8.

3、是否有改动过我库内部的代码?【如有,请说明改动点】 答:没有

bug内容描述

Fatal Exception: NSInternalInconsistencyException 致命异常: NSInternalInconsistencyException Attempted to scroll the collection view to an out-of-bounds item (1017) when there are only 1017 items in section 0. Collection view: <TZCollectionView: 0x117d7dc00; baseClass = UICollectionView; frame = (0 92; 414 720); clipsToBounds = YES; hidden = YES; gestureRecognizers = <NSArray: 0x1690a25b0>; backgroundColor = <UIDynamicSystemColor: 0x117d683c0; name = tertiarySystemBackgroundColor>; layer = <CALayer: 0x1691f9400>; contentOffset: {0, 0}; contentSize: {404, 26068.75}; adjustedContentInset: {5, 5, 5, 5}; layout: <TZRTLLayout: 0x157e4af80>; dataSource: <TZPhotoPickerController: 0x125e28f00>>. 尝试将集合视图滚动到超出范围的项(1017)时,section 0 中只有 1017 项。集合视图: <TZCollectionView: 0x117d7dc00; baseClass = UICollectionView; frame = (0 92; 414 720); clipsToBounds = YES; hidden = YES; gestureRecognizers = <NSArray: 0x1690a25b0>; backgroundColor = <UIDynamicSystemColor: 0x117d683c0; name = tertiarySystemBackgroundColor>; layer = <CALayer: 0x1691f9400>; contentOffset: {0, 0}; contentSize: {404, 26068.75}; adjustedContentInset: {5, 5, 5, 5}; layout: <TZRTLLayout: 0x157e4af80>; dataSource: <TZPhotoPickerController: 0x125e28f00>>。

我如何复现这个bug? 不容易复现

截图

其它说明 有没有其它要补充的?比如你的初始化TZImagePickerController的代码

相关信息: iOS18.5.0 iPhone11 未越狱 屏幕方向:纵向 可用 RAM: 368.67 MB

看了一下内部的代码: TZPhotoPickerController.m 中 969行~978 行

- (void)scrollCollectionViewToBottom {
    TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController;
    NSInteger item = 0;
    if (tzImagePickerVc.sortAscendingByModificationDate) {
        item = [self getAllCellCount] - 1;
    }
    [self->_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:item inSection:0] atScrollPosition:UICollectionViewScrollPositionBottom animated:NO];
    self->_shouldScrollToBottom = NO;
    self->_collectionView.hidden = NO;
}

上述方法中的这个方法出现的问题

[self->_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:item inSection:0] atScrollPosition:UICollectionViewScrollPositionBottom animated:NO];

我现在简单的处理(我猜测的) 设置sortAscendingByModificationDate为 NO了, 这样item 就是 0 了,不会出现越界的问题了

CoderEYLee avatar Jun 18 '25 09:06 CoderEYLee

第二个的崩溃信息 Fatal Exception: NSInternalInconsistencyException Attempted to scroll the collection view to an out-of-bounds item (11047) when there are only 11047 items in section 0. Collection view: <TZCollectionView: 0x117a34e00; baseClass = UICollectionView; frame = (0 92; 414 720); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x165d16010>; backgroundColor = <UIDynamicSystemColor: 0x117968340; name = tertiarySystemBackgroundColor>; layer = <CALayer: 0x165d10840>; contentOffset: {-5, 281694.5}; contentSize: {404, 282409.5}; adjustedContentInset: {5, 5, 5, 5}; layout: <TZRTLLayout: 0x123e56300>; dataSource: <TZPhotoPickerController: 0x122fb9900>>.

CoderEYLee avatar Jun 18 '25 09:06 CoderEYLee

Gemini 已分析了堆栈轨迹和其他指标,以下是分析结果: keyboard_arrow_up sparktip: 希望有更深入的数据分析? 提供更多信息以改进 Gemini 的分析 预览版

开始使用 The crash NSInternalInconsistencyException with the message "Attempted to scroll the collection view to an out-of-bounds item (11047) when there are only 11047 items in section 0" indicates an indexing error. 崩溃 NSInternalInconsistencyException 并带有消息 "尝试将集合视图滚动到超出范围的项目(11047),而该部分中只有 11047 个项目" 表明存在索引错误。 Cause: The issue stems from an "off-by-one" error in array or collection indexing. In programming, if there are 'N' items in a collection, their valid indices range from 0 to N-1. If a collection view reports having 11047 items in section 0, the valid indices for those items are 0 through 11046. Attempting to scroll to item 11047 means you are trying to access an item at an index that is exactly one beyond the last valid index. This is a common logical error where the count of items is incorrectly used as an index. 原因:该问题源于数组或集合索引的“越界”错误。在编程中,如果一个集合中有 'N' 个项目,其有效索引范围是从 0 到 N-1。如果集合视图报告部分 0 中有 11047 个项目,那么这些项目的有效索引范围是从 0 到 11046。尝试滚动到第 11047 个项目意味着试图访问一个超出最后一个有效索引的索引。这通常是一个常见的逻辑错误,其中项目计数被错误地用作索引。 This typically occurs when calling UICollectionView methods that take an IndexPath , such as scrollToItem(at:at:animated:) , and the item component of the IndexPath is set to the total number of items in the section instead of totalNumberOfItems - 1 for the last item, or an invalid value for any other item. 这通常发生在调用 UICollectionView 方法时,这些方法接受 IndexPath 参数,如 scrollToItem(at:at:animated:) ,并且 item 组件的 IndexPath 设置为部分中的总项目数,而不是 totalNumberOfItems - 1 用于最后一个项目,或者任何其他项目的无效值。 Debugging: 调试: Locate Scrolling Calls: Examine the stack trace, and specifically your application's code, for any calls to UICollectionView methods that trigger scrolling. The most common is collectionView.scrollToItem(at:at:animated:) . Other possibilities include manual content offset adjustments or calls to UICollectionViewLayout methods that might indirectly trigger invalid index access. 查找滚动调用:检查堆栈跟踪和应用程序代码,寻找触发滚动的任何 UICollectionView 方法调用。最常见的方法是 collectionView.scrollToItem(at:at:animated:) 。其他可能性包括手动调整内容偏移或调用可能间接触发无效索引访问的 UICollectionViewLayout 方法。 Inspect IndexPath Values: Set a breakpoint at the identified scrolling call site. When the breakpoint is hit, inspect the IndexPath being passed to the scrolling method. Verify the item and section values. You will likely find that the item value is equal to 11047 (or whatever the current item count is for that section). 检查 IndexPath 值:在识别出的滚动调用位置设置断点。当断点被触发时,检查传递给滚动方法的 IndexPath 。验证 item 和 section 的值。你可能会发现 item 的值等于 11047 (或该部分当前项数)。 Review Data Source: Confirm that your collectionView(:numberOfItemsInSection:) method is consistently returning the correct number of items. While the error message indicates the collection view knows the count is 11047, inconsistencies between when the count is determined and when the scroll is attempted can sometimes contribute to such issues, especially with asynchronous data updates. 审查数据源:确认你的 collectionView(:numberOfItemsInSection:) 方法始终返回正确的项数。虽然错误信息显示集合视图知道项数为 11047,但在确定项数和尝试滚动之间的时间差异有时会导致此类问题,尤其是在异步数据更新的情况下。 Actionable next step: 可执行的下一步行动: Identify and Correct Index Calculation: Locate the specific line of code that constructs the IndexPath for the scroll operation. 修正滚动索引计算:定位构建 IndexPath 的具体代码行。 Validate Index: Before calling scrollToItem(at:at:animated:) , ensure that the item value of the IndexPath is always strictly less than the total number of items in that section. 验证索引:在调用 scrollToItem(at:at:animated:) 之前,请确保 item 的 IndexPath 值始终小于该部分中的项目总数。 For example, if you intend to scroll to the last item: 例如,如果你想滚动到最后一个项目: let numberOfItems = collectionView.numberOfItems(inSection: 0) if numberOfItems > 0 { let lastItemIndex = numberOfItems - 1 let indexPath = IndexPath(item: lastItemIndex, section: 0) collectionView.scrollToItem(at: indexPath, at: .bottom, animated: true) } else { // Handle case where there are no items // 处理没有项目的状况 } content_copy If you are trying to scroll to a specific calculated index, always guard against it being out of bounds: 如果你尝试滚动到某个特定的计算索引,请始终防止该索引越界: let targetItemIndex = someCalculatedIndex let numberOfItems = collectionView.numberOfItems(inSection: 0)

if targetItemIndex >= 0 && targetItemIndex < numberOfItems { let indexPath = IndexPath(item: targetItemIndex, section: 0) collectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: true) } else { // Handle invalid index, e.g., log an error or prevent scroll // 处理无效的索引,例如记录错误或防止滚动 } content_copy Best practices to avoid such issues: 避免此类问题的最佳实践: Strict Index Validation: Always validate indices before using them to access elements in collections or when passing them to APIs like UICollectionView.scrollToItem . This means ensuring 0 <= index < count 严格索引验证:在使用索引来访问集合中的元素或传递给类似 UICollectionView.scrollToItem 的 API 之前,始终要验证索引。这意味着确保 0 <= index < count . Consistent Data Source: Ensure that your data source (the array or model backing your collection view) and the collectionView(:numberOfItemsInSection:) method are always synchronized. Any changes to the data model must be reflected in the collection view's UI by calling reloadData() or using performBatchUpdates 一致的数据源:确保你的数据源(支持集合视图的数组或模型)和 collectionView(:numberOfItemsInSection:) 方法始终同步。任何对数据模型的更改都必须通过调用 reloadData() 或使用 performBatchUpdates 反映在集合视图的 UI 中。 . Asynchronous Data Handling: If your data is loaded or updated asynchronously, ensure that scroll operations are only attempted after the collection view has finished updating its layout and its internal state reflects the new data. For example, if you call reloadData() , you might need to dispatch the scroll operation to the next run loop cycle or use collectionView.layoutIfNeeded() before scrolling, though careful synchronization with performBatchUpdates completion blocks is often more robust. 异步数据处理:如果你的数据是异步加载或更新的,在尝试滚动操作之前,确保集合视图已完成其布局更新,并且其内部状态反映了新数据。例如,如果你调用了 reloadData() ,你可能需要将滚动操作调度到下一个运行循环周期,或者在滚动之前使用 collectionView.layoutIfNeeded() ,尽管与 performBatchUpdates 完成块的仔细同步通常更为可靠。 Guard Against Empty States: Always consider edge cases where a section might be empty (contains 0 items) before attempting to calculate an index like count - 1 防止空状态:在尝试计算类似 count - 1 的索引之前,始终考虑部分可能为空(包含 0 项)的边缘情况。 .

CoderEYLee avatar Jun 18 '25 09:06 CoderEYLee

原始代码:

- (void)prepareScrollCollectionViewToBottom {
     if (_shouldScrollToBottom && _models.count > 0) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self scrollCollectionViewToBottom];
            // try fix #1562:https://github.com/banchichen/TZImagePickerController/issues/1562
            if (@available(iOS 15.0, *)) {
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    [self scrollCollectionViewToBottom];
                });
            }
        });
    } else {
        _collectionView.hidden = NO;
    }
}

修改后的代码:(这个代码我在 iOS18.4.1+iPhone 13 Pro, 只刷新一次正常滚动到底部是没有问题的) 将原来的 15.0+的刷新 2 次修改为刷新一次了 这个代码得需要充分测试, 这个作者决策一下吧,

- (void)prepareScrollCollectionViewToBottom {
    if (_shouldScrollToBottom && _models.count > 0) {
        NSTimeInterval delay = 0.01;
        if (@available(iOS 15.0, *)) {
            delay = 0.1;
        }
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self scrollCollectionViewToBottom];
        });
    } else {
        _collectionView.hidden = NO;
    }
}

CoderEYLee avatar Jun 18 '25 10:06 CoderEYLee

这个测试的效果怎么样了? 我这边也有这个崩溃了

helinyu avatar Oct 10 '25 06:10 helinyu

Image我的能崩溃的基本设备系统信息

helinyu avatar Oct 10 '25 06:10 helinyu

我最后的方案是将 sortAscendingByModificationDate 设置为 NO了, 但是这个不是崩溃的根本原因, 你可以按照上面我说的那种方式修改一下, 或者自己分析原因, 最好开一下 AB实验

CoderEYLee avatar Oct 20 '25 09:10 CoderEYLee