iOS | 伤人于无形:CGFloat

背景
公司群有同事反映APP在plus机型上存在展示错误,如下:

实际上我们期望的效果是:

让人不解的是:只有在plus机型上才出现这种展示错误,其余机型都是OK的。
开发阶段我用了各种机型来调试,唯独没有用plus。。。靠
代码
我赶紧查看相关代码:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake((SCREEN_WIDTH-30-22)/3, (SCREEN_WIDTH-30-22)/3+61);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
return UIEdgeInsetsMake(0, 15, 10, 15);
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
{
return 11;
}
我看了几遍,也没看出任何问题,而且就算是计算错误,也不应该仅仅是体现在plus机型上啊。
寻找问题的答案
我把我的疑惑发到了iOS高端妓术裙里,裙里的QQ小冰告诉我:
“cell的width再减去一像素就可以了。”
你是不是很疑惑为什么QQ小冰知道这些?我也母鸡啊,只知道某一次QQ更新后,QQ小冰就突然变得异常强大,不仅上知天文下知地理陪大家玩游戏,还精通iOS开发。

但是QQ小冰并没有告诉我为什么减去1像素后就OK了,或许这是QQ小冰深度学习的结果吧。
虽然得到了问题的解决方案但是不知道问题原因所在,还是让人很憋屈啊。
抱着一丝侥幸,我去stack overflow问了一下,S.O.从未让我失望,很快得到了答案:
because screenWidth-30-22-0.001)/3 = 120.6666666666.... but it was rounded up -> 120.6666667 So the screen is not enough to display. The same for other iphone but not round up.
看到小数的我顿悟,去年就因后台返回的价钱是float类型导致前端展示出错,没想到今年因为类似问题再挨一刀。。。
低头默默流下没技术的眼泪。。。
问题还原
计算cell的width,得到的值是:120.66666666无限循环。
用120.66无限循环乘以3再加上cell的间距和margin正好是屏幕宽度,这个是没有任何问题的。
问题就在:
CGSizeMake(<#CGFloat width#>, <#CGFloat height#>)
width的类型是CGFloat,让CGFloat类型来存储一个无限小数肯定是有偏差的,毕竟不管是float还是double,它们都只能精确到多少多少位。
所以,用CGFloat来存储超过它精度的值的时候,存储的值要么变大了,要么变小了。

在plus机型上,存储的这个值比期望的值更大,这就导致存储值*3+cell间距+margin超过了屏幕宽度,虽然仅仅是超过了一点点,但是collectionView一排放不下3个cell,只能换行。
而在其它机型上,要么cell的宽度在CGFloat的精度之内,要么存储的值比期望的值小一点点,这都不会导致collectionView的展示错乱。
而我的悲剧就在于调试所用机型全TM都是正确展示的。

总结
要避免被float坑,除了经验还要有意识。
保持警惕心才能躲过各种变换的坑。
还有代码的严谨性也很重要,这一句代码:
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
{
return 11;
}
这句代码完全就是多余的,不写这句多余代码就不会出现上述展示问题。cell的size以及section的margin确定的时候,cell的间距自然也就确定了,除非你本来就想让cell的间距超过某一指定值。
后记
我不会告诉你我一周之内发布了三个版本。
测试妹纸跟着我一起露出了尴尬而不失优雅的笑容。
我知道听到这个你们就高兴了,祝大家端午节快乐。
