崩溃提示:Invalid row height (-XXX) provided by table delegate. Value must be at least 0.0, or UITableViewAutomaticDimension.
全局防护:FixInvalidRowHeight 将 UITableView (FixInvalidRowHeight) 拖到项目中即可
字面意思就是说 rowHeight 不能为负数,否则会导致内部计算错误。
正常来说写代码时也不会说返回个高度为负数的 rowHeight,可能是如下这种代码导致的
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
retrue tableView.height - 100;
}
假如在 tableView 进行正确的 layout 前就调用了 heightForRowAtIndexPath 方法,此时 tableView.height 为 0 导致算出来的 rowHeight 为负数。
防护措施一:排查代码的调用时机有没问题,保证在 tableView 的高度没值时不会触发 heightForRowAtIndexPath代理方法
防护措施二:上面排查完还不放心的话加个 Max 就好了
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
retrue MAX(tableView.height - 100, 0);
}
防护措施三:(最后防线)
因为在 iOS13 上如果返回负数是会导致直接闪退的,就算你的是像上述代码导致的负数,在重新 layout 是可以拿到 tableView 的正确高度后返回一个正确且不小于 0 的 rowHeight 也没用,因为 App 已经闪退,因此就有了如下的防护措施。
通过崩溃堆栈可以分析出苹果是在方法 _dataSourceHeightForRowAtIndexPath 或 _classicHeightForRowAtIndexPath 中抛出一个 Exception,因此 hook 此次的 exception 然后 return 即可,从而可以继续后续的 layout 得到正确的 rowHeight 进行布局。
FixOverrideImplementation(object_getClass(NSException.class), @selector(raise:format:arguments:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {
return ^(NSException *selfObject, NSExceptionName raise, NSString *format, va_list argList) {
if (raise == NSInternalInconsistencyException &&
[format containsString:@"Invalid row height"] &&
[format containsString:@"provided by table delegate. Value must be at least 0.0, or UITableViewAutomaticDimension"]) {
NSLog(@"%@",format);
// TODO 这里将错误上报到自己的崩溃收集平台(如 bugly)然后解决问题,忽略崩溃并不是本意,意在收集问题然后进行解决
// [Bugly reportException:selfObject];
return;
}
void (*originSelectorIMP)(id, SEL, NSExceptionName name, NSString *, va_list);
originSelectorIMP = (void (*)(id, SEL, NSExceptionName name, NSString *, va_list))originalIMPProvider();
originSelectorIMP(selfObject, originCMD, raise, format, argList);
};
});
记得在崩溃处将 exception 上报,毕竟忽略崩溃并不是本意,意在收集问题然后进行解决。
网友评论