pop动画的严重性能问题
之前一个界面打开的时候有一点卡,直觉告诉我可能是pop动画的锅,一调试,果然,pop的renderTime
这个函数耗时太久了。
这一个页面用了6,7个POP动画,我的看法是很多大多数动画能用CA实现的,就不要用POP。
iOS动画渲染的机制是这样的,如果是一个CA隐式动画,计算好要数据之后会提交给一个backBoard后台进程,然后我们APP就不管了,这意味着这个动画的后续的每一帧计算我们已经不用再提交了。而POP相反,每一帧都要去提交backBoard后台进程(核心是CADisplaylink
),就产生了性能问题。
顺便提一句动画是为了让用户更好的交互,比如一个工具栏会在右边隐藏,那么开场的飞入动画能提示用户右边有个工具栏。
每一个动画应该都有他的交互意义存在,不是单纯为了炫酷。比如写出lottie动画库的airbnb,无意义动画太多,就导致app看上去很乱,很卡,甚至首页的列表滑动的时候出现了卡帧,性能问题没解决,做那么多炫酷的动画难免有点头重脚轻。
各种动画的适用场景,什么时候用约束做动画
做动画无非两板斧:先解构动画组成,再一步步实现。
但是不同的场景有不同的实现,ca也好,pop也好,改transform
也好。但以前总觉得用约束做动画是一个异类,没啥好处,却很诡异。
这一期却让我学到了约束动画的好处-联动。
具体业务场景不描述了,原本是打算做一个假的透明的headview没能实现需求。
用约束却很容易也很完美地实现了。
其实用什么做动画都可以,具体场景具体分析。但尽量不要改变frame去做动画。
简单地更改响应链,以配合UI的反人类设计
场景:先想象一个简单的表,有headview,但是上滑的时候headview是被表盖住,并且!他能响应交互事件。
实现:如果不响应相互事件,只要简单的将headview写成一个独立view,再给表一个透明的与独立view一样大小的view即可。
其实要响应交互也很简单,只要部分禁用表的交互就可以了,如下:
需要注意的是判断frame的时候,要取透明view相对于整一个视图的坐标,需要转换坐标系。
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
UICollectionReusableView *reusableView = [self.mainCV supplementaryViewForElementKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
NSLog(@"+++%f",self.mainCV.contentOffset.y);
if (reusableView) {
CGRect clearViewFrame = reusableView.frame;
clearViewFrame.origin.y = clearViewFrame.origin.y -self.mainCV.contentOffset.y;
BOOL isContained = CGRectContainsPoint(clearViewFrame, point);
if (isContained) {
return NO;
} else {
return [super pointInside:point withEvent:event];
}
} else {
return [super pointInside:point withEvent:event];
}
}
实现多个竖排文字的排列
最近在解决一些组里遗留下的一些没解决的BUG。
之前同事的解决方法是写一个分类,每一个字/n换行。这个网上也一查就能查到,但是。。。只能显示1列。
我的建议是因为之前看到过YYText有类似的实现,只不过从从右到左排列。可以使用YYText或者自己使用CoreText,但同事反馈有时候渲染不出来,我对此存疑但也没有深究。
这期我花时间看了一下之后,采取的方法是使用苹果自带的NSStringDrawing,我先计算出每一个字需要在的位置和大小,再用以下的方法将字绘制出来。这种办法无论几列,无论从左到右,从上到下,从右到左都可以实现。也比CoreText上层的多。
@interface NSString(NSStringDrawing)
- (CGSize)sizeWithAttributes:(nullable NSDictionary<NSAttributedStringKey, id> *)attrs NS_AVAILABLE(10_0, 7_0);
- (void)drawAtPoint:(CGPoint)point withAttributes:(nullable NSDictionary<NSAttributedStringKey, id> *)attrs NS_AVAILABLE(10_0, 7_0);
- (void)drawInRect:(CGRect)rect withAttributes:(nullable NSDictionary<NSAttributedStringKey, id> *)attrs NS_AVAILABLE(10_0, 7_0);
@end
UITabview或者UICollectionView数据错乱的问题
一个复杂的UICollectionView数据错乱的bug,最后发现居然只是赋值的时候写了xx.view.xx属性=新属性,但view没有任何的接受方法(比如一个set方法更新UI),只在layoutview里写了赋值。
体会如下:遇到数据错乱先去调试赋值的时候model是否正确(一般不会出现数据源错乱的),基本都是因为赋值到UI的时候出现的问题。
比如下载图片的时候判断了url的length>0才开始下载,else却什么都没写,else难道不用将图片置为默认图片吗。这个问题这份代码里也存在。
还有一个情况是如果用了异步下载更新图片,当cell滑出屏幕,进入重用池,再被唤起,上一个下载图片的任务才执行完毕,回调更新UI,就更新到了错误的Cell上。所以SDWebimage采取了滑出屏幕取消任务的做法。
cell写许多block回调,同样道理,如果没有做处理,在将cell滑出屏幕的时候,就会出现问题。(之前我这么写没发现问题,需要review一下代码)
对3D旋转的理解
CATransform3D trans = CATransform3DIdentity;
trans.m34 = -1/100.0;
trans = CATransform3DRotate(trans, M_PI/10.0*yOffset, 0, 1, 0);
attr.transform3D =trans;
trans.m34 = -1/100.0; 是开启透视效果,0, 1, 0可以看做一个向量,所以010和050是没有区别的,但这种办法做的3d旋转view的边上会有锯齿,暂时不知道怎么解决。
对重写layout两个方法的理解
因为项目里最近遇到的动画,和不同的布局越来越多,自己重写的layout已经有3个。以前一直分不清两个方法的区别。
返回对应于indexPath的位置的cell的布局属性
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
返回rect中的所有的元素的布局属性
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
有的人只重写第二个方法,有的重写2个,第二个的实现依赖第一个。
其实:layoutAttributesForElementsInRect:的rect是可见部分的rect;layoutAttributesForItemAtIndexPath:是对于任意indexpath而言的。前面一个的实现往往依赖于后者,根据rect取得可见的indexpath,再根据后者取得layout attributes,加入数组,然后返回。
KVO无法捕捉动力动画中的小球的frame变化
KVO的本质无非是isa指向一个新的派生类,在派生类的set方法里添加副作用
为什么不在原来的类里添加?原因很明显,太多的副作用会影响set的纯洁性。
题目的原因是动画过程中的view 他本身的frame实际上没有发生变化。
实现uicollectionview的headview 悬浮
最简单的方法是iOS9之后有一个bool值可开关。但无奈现在还要适配iOS8,唯一的办法是重写一个layout
但大部分人如果用了mjrefresh会有冲突的,上拉的时候会有一闪的效果,主要还是因为mj改了conteninset,我的解决方法自己简单写了一个上拉。舍弃了mj。
可参考这个第三方:
https://github.com/CSStickyHeaderFlowLayout/CSStickyHeaderFlowLayout
我用的自己写的。感觉此库已经年久失修。
并且重写layout已经不是新鲜的做法了,许多酷炫的效果都可以通过这个方法实现。
如何reload collectionview 但是只改变高度 不走数据源代理
因为需求中有一个是动态改变headview的高度 当然如果是一个headview的话,直接改layout即可,但这里的需求有多个。
我一开始简单地reload了collectionview,但是会有一闪的感觉,网上也没说有好的办法。
后来我去查看了collectionview的接口找到这样一个方法。效果很好。
[self.cvDiamond setCollectionViewLayout:self.layout animated:YES];
AVPlayer与系统pop右滑手势的冲突
如果你写过视频,肯定用过苹果原生的AVPlayer,那么我相信你们家只要有测试,就会发现这样一个bug。在视频页面右滑返回上一页面,左右来回移动,不真正的返回,回来会有卡帧。这是苹果自己AVPlayer的bug。
有一个解决方法。AOP替换方法,替换pop手势,替换viewdidload,在每打开一个界面的时候保留一张截图,左滑返回的时候并不是真的返回,而是用pan手势,改变frame,先显示上一个界面的截图,等手势结束判断返回上一个界面还是停留。
具体写法参考ZFPlayer,git上有。
但我个人觉得这个方案不好,因为每个大一点的app都会有自己的nav,都会有自己的左滑效果。这样一来这个效果就没了。
当然你也可以简单的暂停播放。
SVHud 不断重复调用 的BUG
一个场景:SVHud (对,就是那个出名第三方),在PDF下载前的有进度的HUD 会出现消失很缓慢的情况,简单地说就是PDF已经下载结束了,他还在转动。
原因:看了源代码,他是使用了NSOperation不断地去添加重绘的任务,然而AFNetworing的下载回调是0.00001数量级s就有一次的,因此导致前一个绘制任务还没结束就不断继续添加。主要还是SVHud得BUG,没有做取消操作。其他组同事写的图片上传的没有出现类似问题是因为,上传图片我们用的又拍云,它对上传进度回调做了处理,每0.1数量级左右才回调一次。
解决办法:自己写了一个HUD。
scrollview自动适应导致真实位置上下偏移
场景:导航栏隐藏,自定义View成为导航栏。在一个ScollView中嵌套左右可以滑动的2个tabview,下拉刷新的时候发生了奇怪的抖动。
原因:因为scrollview不能上下滑动的前提是contensize.height==tabview.height
。
而这个automaticallyAdjustsScrollViewInsets
属性会导致scrollview的inset在我不知道的情况下发生了变化。
所以两个视图的手势都响应了。tabview 和scrollview的手势冲突了,两个都响应了滑动。ios9 和ios10 应该都有问题。
解决办法是:self.automaticallyAdjustsScrollViewInsets = NO
; 改了这个属性就好了,iOS 11中automaticallyAdjustsScrollViewInsets
属性被废弃了,self.automaticallyAdjustsScrollViewInsets = NO
就等于没有设置(默认是YES),于是顶部就多了一定的contentInset。
如果你的APP中使用的是自定义的navigationbar
,隐藏掉系统的navigationbar,并且tableView的frame为(0,0,SCREENWIDTH, SCREENHEIGHT)开始,那么系统会自动调整SafeAreaInsets值为(20,0,0,0)。
解决方法:
if (@available(iOS 11.0, *)) {
self.xxView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} else {
self.automaticallyAdjustsScrollViewInsets = NO;
CAAnimation 由协议变成了 强引用的delegate
在iOS10之后,CAAnimation
由协议变成了 强引用的delegate,为什么是强引用呢?
苹果官方也说是一个特例,但我们并不需要担心它的循环引用。
因为animation是跟随layer->view的生命周期的,需要特定的设置释放,所以循环引用的问题不作考虑。
SDWebImage 图片错乱的问题
一个比较常见的问题:cell中的图片错乱,原因主要是2点1:自己写了图片下载,没有取消滑出屏幕的cell下载任务。
2:自己对图片异步做了裁剪,更新的时候没处理好。
解决办法1:使用SDWebimage 因为他处理了这个情况,滑出屏幕的任务会取消2:使用url匹配,在下载回调回来之后的block里比较cell的url和图片url是否匹配。
reloaddata 回到顶部
relaodData
不会滑动到顶部,是因为新的数据是加在后面的,这个时候relaodData确实不会滑动
但是你试试将数据insert到前面,然后relaodData,就会滑到顶部
只好刷新单行
阴影在两个情况下不会渲染
1背景是clear color
2view设置了cliptobounds
如果设置增加cv的contentsize
为了满足各种奇特的UI 需要将表的数据扩充到一个屏幕那么大
1在reloaddata之后并不能立刻拿到正确的contentsize 要调用[self.mainCV layoutIfNeeded];
2因为头部加了一个透明的headview
计算contensize
的时候 会将这个高度高度(headview)计算进来