Random UITableView NSRangeException
Recently I'm building a page to display long images for product descriptions. The image was split in several short images for better performance. I download the image in ProductImageCell
and use closure didUpdateHeight
to tell the UITableView
to refresh the cell's height.
class ProductImageCell: UITableViewCell {
@IBOutlet weak var productImageView: UIImageView!
var didUpdateHeight: ((CGFloat, IndexPath) -> Void)?
func setup(imageLink: String, indexPath: IndexPath) {
productImageView.kf.setImage(with: URL(string: imageLink)) { (image, error, type, url) in
if let image = image {
let ratio = image.size.width / UIScreen.width
let height = ceil(image.size.height / ratio)
self.didUpdateHeight?(height, indexPath)
}
}
}
}
With the below code to refresh the cell's height, everythings seem to be working perfectly.
cell!.didUpdateHeight = { [unowned self] height, indexPath in
self.heightCache[indexPath] = height
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
Then I got a bunch of crash reports from some minor iOS versions, like 10.3.1, 11.0.1, 11.0.2.
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 4 beyond bounds [0 .. 3]'
*** First throw call stack:
(
0 CoreFoundation 0x000000010eafdb0b __exceptionPreprocess + 171
1 libobjc.A.dylib 0x000000010b8c3141 objc_exception_throw + 48
2 CoreFoundation 0x000000010ea32ffb -[__NSArrayM objectAtIndex:] + 203
3 UIKit 0x000000010c5bfd60 -[UITableView _updateVisibleCellsNow:isRecursive:] + 4676
4 UIKit 0x000000010c5f3ccc -[UITableView _performWithCachedTraitCollection:] + 111
5 UIKit 0x000000010c5dae7a -[UITableView layoutSubviews] + 233
6 UIKit 0x000000010c54155b -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1268
7 QuartzCore 0x000000011239a904 -[CALayer layoutSublayers] + 146
8 QuartzCore 0x000000011238e526 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 370
9 QuartzCore 0x000000011238e3a0 _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 24
10 QuartzCore 0x000000011231de92 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 294
11 QuartzCore 0x000000011234a130 _ZN2CA11Transaction6commitEv + 468
12 QuartzCore 0x000000011234ab37 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 115
13 CoreFoundation 0x000000010eaa3717 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
14 CoreFoundation 0x000000010eaa3687 __CFRunLoopDoObservers + 391
15 CoreFoundation 0x000000010ea88720 __CFRunLoopRun + 1200
16 CoreFoundation 0x000000010ea88016 CFRunLoopRunSpecific + 406
17 GraphicsServices 0x0000000111bc4a24 GSEventRunModal + 62
18 UIKit 0x000000010c47e134 UIApplicationMain + 159
19 AutopartsStoreLib-Demo 0x00000001095eaed0 main + 48
20 libdyld.dylib 0x000000010fb5b65d start + 1
21 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
I don't have any clue what's wrong, because I wasn't making any kind of NSRangeException
mistakes. But finally I pinpointed the cause by commenting code part by part.
// Crashing!!!
self.tableView.beginUpdates()
self.tableView.endUpdates()
Why is this code crashing my app? It looks like a bug in iOS. Thanks to this blog post Apple, c’mon with the docs!, I found out actually it's not a reliable way to refresh your cells' heights with beginUpdates
and endUpdates
. The risk is stated in Apple's doc, even it's only one line.
If you do not make the insertion, deletion, and selection calls inside this block, table attributes such as row count might become invalid.
How can I implement my feature when the go-to UI component don't work? I re-implemented the feature with UICollectionView
in the end, it works well.