引文:
照片浏览滑动效果UIScrollView和UIPageControl组合 -- tada
使用UIScrollView 结合 UIImageView 实现图片循环滚动 -- 两个和三个ImgView哪个性能更好?
UIScrollView新手教程 -- 不错
理解Scroll View -- 光栅化和组合,原理好文
计算机图形渲染的流程 -- 知识拓展,很通俗
绘制像素到屏幕上 -- 底层绘图
ScrollView 与 Autolayout -- 一个坑,应该先加一个contentView
UIScrollView 实践经验 -- 好好领悟
UIScrollView
首先正好说一下 frame 和 bounds的区别:
contentOffset
巧妙的通过改变scrollView的bounds,每个单独的子视图都被移动了。这正是一个scrollView的工作原理。当设置contentOffset时,它改变scrollView.bounds.origin。(子视图的frame是相对于父视图bounds的布局)
contentSize
contentSize > bounds,可以滚动视图
contentOffset就等同于bounds.origin
contentInsets
contentInset属性可以改变contentOffset的最大值和最小值,这样便可以滚动出可滚动的区域。新手小例子中用contentInsets来控制缩小的图片居中。
UITableView 刷新原理
tableView为了适应每个cell,可滚动区域是通过精心计算的。当你滚动经过tableView的第一个或最后一个cell边界时,tableView将contentOffset弹回并复位,所以cells又一次紧贴scrollView的bounds。所以,必须将�refresh control放在可滚动区域的上方。这将允许首先contentOffset弹回第一行。
当向下拉动出足够多的区域时,tableView设置contentInsets,扩大了contentOffset的区域,refreshControl的区域就被包含进来。刷新完成后,contentInsets恢复原始值,contentOffset也恢复。(EGOTableViewPullRefresh实现)
小应用
当键盘出现在界面上的时候,挡住了scrollView原本显示的一部分。通过设置contentInsets.bottom = 键盘的高度,可以暂时扩大contentOffset的向下最大可视范围,从而能拖动看到被键盘挡住的那部分。键盘消失时,再恢复contentInsets。
ScrollView && AutoLayout
对于 UIScrollView 的 subview 来说,它的 leading/trailing/top/bottom space 是相对于 UIScrollView 的 contentSize 而不是 bounds 来确定的,所以当你尝试用 UIScrollView 和它 subview 的 leading/trailing/top/bottom 来互相决定大小的时候,就会出现 「Has ambiguous scrollable content width/height」的 warning。
正确的姿势是用 UIScrollView 外部的 view 或 UIScrollView 本身的 width/height 确定 subview 的尺寸,进而确定 contentSize。
因为 UIScrollView 本身的 leading/trailing/top/bottom 变得不好用,所以我习惯的做法是在 UIScrollView 和它原来的 subviews 之间增加一个 content view。
delegate调用顺序
scrollViewDidScroll: -- 在任何方式触发 contentOffset 变化的时候都会被调用
scrollViewWillBeginDragging: -- dragging noDecelerating
scrollViewWillEndDragging: withVelocity: targetContentOffset: -- dragging noDecelerating
scrollViewDidEndDragging: willDecelerate: -- dragging noDecelerating
scrollViewWillBeginDecelerating: -- dragging decelerating
scrollViewDidEndDecelerating: -- noDragging noDecelerating
分页优化
pagingEnabled
缺点蛮多,具体padding还不懂
Snap
最后没到位,动画会突然到位,是突兀
targetContentOffset
更平滑,之前计算好,动画自然
视觉差
通过 scrollViewDidScroll 实时改变另一个scrollView的contentOffSet,和新手教程里的原理类似。
重用
维护一个重用队列
当元素离开可见范围时,removeFromSuperview 并加入重用队列(enqueue)
当需要加入新的元素时,先尝试从重用队列获取可重用元素(dequeue)并且从重用队列移除
如果队列为空,新建元素
这些一般都在 scrollViewDidScroll: 方法中完成
note that:
当重用对象为 view controller 时,记得 addChildViewController
当 view 或 view controller 被重用但其对应 model 发生变化的时候,需要及时清理重用前留下的内容
数据可以适当做缓存,在重用的时候尝试从缓存中读取数据甚至之前的状态(如 table view 的 contentOffset),以得到更好的用户体验
当 on screen 的元素数量可确定的时候,有时候可以提前 init 这些元素,不会在 scroll 过程中遇到因为 init 开销带来的卡顿(尤其是以 view controller 为重用对象的时候)
引文:
触摸事件:
touchesBegan:withEvent:
touchesMoved:withEvent:
touchesEnded:withEvent:
touchesCancelled:withEvent: -- 电话导入取消
(1) 单tap下,touches == event.allTouches。 两指,event.allTouches.count == 2;
(2) 连tap两下,touches -> touch -> tapCount == 2 -- 想到很多连击游戏,如果可以MS到方法里把tapCnt改个100。
(1)UIView中:
initWithFrame:-- 初始化
layoutSubviews -- 布局 (setNeedsLayout)
CGGeometry.h -- CGRectGetMaxX
(2)首先将编译选项改为 OC++。
如果头文件中用 @class 声明一个类,那么这个类中的对象在被外界访问的时候就会出现 Member access into incomplete type "~~"`
(3)子类化UIScrollView实现对Cell的布局 -- 高度和Y值进行布局
(4)Cell的重用 -- visibleCells(删除入队) 和 cacheCells(获取出队)
和VC重用思路类似。
在要使用一个Cell的时候我们先去看看tableView中有没有可以重用的cell,如果有就用这个可以重用的cell,只有在没有的时候才去创建一个Cell。这就是享元模式。
享元模式可以理解成,当细粒度的对象数量特别多的时候运行的代价会相当大,此时运用共享的技术来大大降低运行成本。比较突出的表现就是内容有效的抑制内存抖动的情况发生,还有控制内存增长。它的英文名字是flyweight,让重量飞起来。
(5)响应和处理事件 -- addGesture (点击位置与cellFrame)
(6)接口和数据获取 -- protocol (数据源,点击事件)
(7)选中态 -- 事件方法判断,backgroundView展示
UITableView 滚动流程性优化 -- O(1)
详细整理:UITableView优化技巧 -- 用到很多不熟悉的方法
提升UITableView性能-复杂页面的优化 -- 类似
UIScrollView 实践经验-- 好棒几个例子,最佳技巧UITableView
iOS 保持界面流畅的技巧 -- ibib
一次 TableView 性能优化经历
10个加速Table Views开发的Tips
阿峥教你实现UITableView循环利用
UITableView
1. 快速滑动
当用户手动拖动tableView时,加载cell图片。
当用户快速滑动的减速过程中, 有缓存就加载,没缓存不加载cell图片。
targetContentOffset位置直接设置加载,滚动到的时候已经开始加载。
2. LazyLoad
- fetchDataFromServer - AFHTTPRequestOperationManager
- heightForRowAtIndexPath
- setupCell: withIndexPath: -- SDWebImageDownloader
- loadImageForVisibleCells
- scrollViewDelegate - 3
3. Cell重用机制
[tableView dequeueReusableCellWithIdentifier:(NSString)identifier forIndexPath:(NSIndexPath)indexPath];
(1) Storyboard: 定义Cell的Prototype,并设置其Reusable Identifier
(2) Xib自定义: [registerNib:(nullable UINib)nib forCellReuseIdentifier:(NSString)identifier];
(3) 代码自定义:[registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier];
重写自定义cell的 initWithStyle:withReuseableCellIdentifier: 方法进行布局。
自定义cell时,记得将其他内容加到self.contentView 上,而不是直接添加到 cell 本身上。
4. 设计统一规格的Cell
- 等高的Cell好设计
- 动态计算高度的Cell也应该统一设计
5. 提前计算并缓存cell的UI尺寸信息
创建ViewModel,计算并储存Cell的UI尺寸信息
tableView:heightForRowAtIndexPath: --> tableView:cellForRowAtIndexPath
使用了ViewModel来保存UI信息,Cell 高度的计算 和 使用的时机 需要特别留意。
6. 圆角,阴影,mask
(1) Cell中的view尽可能不要使用透明 -- opaque不透明的话,绘画的时候就不会去画下层的视图。尽量减少cell中子视图透明化以及做切圆操作,在layer层渲染图层时会涉及上下文切换以及离屏渲染之类的,系统开销会很大,特别是在cell视图很复杂的时候,由于渲染问题导致的内存开销会让你的tableview非常卡顿。比如cell中需要设置头像圆形直接设置圆角会很卡,一般用CG把拿到的图片处理一遍在给cell使用就好了。圆角、阴影之类的全部 bitmap 化,或者放到后台 draw 好了再拿来用
(2) 用代码自定义的cell,使用时要做 layer 栅格化处理
(3) 减少子视图的层级关系
(4) 图片载入在后台进程进行,滚出可视范围的载入进程cancel掉
(5) 后台对图片先进行解码
UIImageView 的载入是惰性的说法,是对的。但是大部分开发者都没有正确理解这一点。下面就详细解释一下:
[UIImage imageWithContentOfFile:] 出来的 UIImage 其实并没有真正把文件解码到内存,而是要等到用的时候(例如去显示或者去 scale)才会去做这件事情。但问题就在于 UIImageView 试图去 draw 图片的时候,它读文件、渲染也是在主线程里做的,所以你要读入的图片如果很大(比如 iPad3 上的 @2x 图),这一步就很容易会卡一下。这也就是为什么我说图片要放到后台进程去解码完之后,在主线程显示。
iOS中线宽与像素的关系 -- 补补补
UITableViewCell
iOS7
estimatedRowHeight, SectionHeaderHeight, SectionFooterHeight iOS7中每个cell的高度会被系统自动缓存起来,不会再重复计算了
[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; 可以通过约束计算到cell的高度。要求使用者对约束设置的比较熟练,要保证 contentView 内部上下左右所有方向都有约束支撑,设置不合理的话计算的高度就成了0。
另外需要注意的是, 在iOS7下, 如果布局中有UILabel, 并且行数大于0时, 需要指定preferredMaxLayoutWidth, 这样Label才能知道自己什么时候该换行, 然后-systemLayoutSizeFittingSize才能得到正确的高度.
另一种方案就是,给cell的contentView添加一个和tableView宽度相同的宽度约束, 这样就能在UILabel约束完备的情况下算出UILabel的宽度.(因为contentView宽度的计算需要知道子控件宽度的累加,而UILabel的换行却依赖着contentView的宽度,不换行就不知道UILabel的高度!)
iOS8
self-sizing cell
2. 制作一个可以滑动操作的 Table View Cell
创建一个自定义 Cell
-- button1 button2 点击传递给delegate去处理 -- 添加一个 Delegate
-- contentView 用于覆盖,Label用于显示
为按钮添加 Action
showDetailWithText: -- presentDetailNav
添加顶层视图并添加滑动 Action
添加数据 -- labelText由cell.label托管
手势识别
-- panRecognizer - panThisCell:
-- panStartPoint 开始点,判断左右滑动
-- startingRightLayoutConstraintConstant 右约束
-- contentViewLeftConstraint & contentViewRightConstraint
移动这些约束
buttonTotalWidth
- (void)resetConstraintContstantsToZero:(BOOL)animated notifyDelegateDidClose:(BOOL)endEditing
- (void)setConstraintsToShowAllButtons:(BOOL)animated notifyDelegateDidOpen:(BOOL)notifyDelegate
UIGestureRecognizerStateBegan & UIGestureRecognizerStateChanged
Snap!
- (void)updateConstraintsIfNeeded:(BOOL)animated completion:(void(^)(BOOL finished))completion
set & reset
UIGestureRecognizerStateEnded & UIGestureRecognizerStateCancelled
更好地处理 Table View
Gesture冲突 -- shouldRecognizeSimultaneouslyWithGestureRecognizer:
重用cell出现打开 -- prepareForReuse
- (void)cellDidOpen:(UITableViewCell *)cell;
- (void)cellDidClose:(UITableViewCell *)cell;
- (void)openCell;
set & reset + delegate方法
cellsCurrentlyEditing -- 打开添加关闭移除没问题,打开cell状态滑动tableView,会进行重用会调用cellForRowAtIndexPath,所以检测是否包含在currentlyEditing数组中,如果在就
3. 很炫的table view cell切换效果 SvpplyTable
看到简单函数内部又调用复杂函数,现在终于想明白了,是为了对外提供简介的API,然后复杂的逻辑在自己内部做。我以后也要这么干。
如果多次跳转调用函数,传递参数名尽量不要变,不然会给阅读增加负担。
_ivar 比 self.ivar 的一个好处在于调试时候可以更容易看到值
4. 利用长按手势移动 Table View Cells -- 小而美
UICollectionView
UICollectionView Tutorial Part 1: Getting Started
UICollectionView Tutorial Part 2: Reusable Views and Cell Selection
现在,UICollectionViews有了简单的重排功能 -- 太棒
叶孤城:UICollectionView自定义布局教程——Pinterest
UICollectionView 高级进阶篇 -- 各种酷炫
自定义 Collection View 布局 -- 中规中矩
UICollectionView Custom Layout Tutorial: A Spinning Wheel -- 旋转视图, 改变锚点, 自定义 collection view layout
Vertical -- from left to right,horizontal -- from top to bottom
Supplementary views 和 decoration views 必须是UICollectionReusableView的子类。
header && footer
UICollectionViewController,UICollectionView,UICollectionViewCell,UICollectionReuseableView,UICollectionViewLayout,UICollectionViewLayoutAttributes
- (void)prepareLayout -- 这个整个布局过程中最重要的方法之一。因为这里可以创建和存储layout attributes。
- (CGSize)collectionViewContentSize
- (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect
- (UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath*)indexPath
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
动画:
插入和删除
initialLayoutAttributesForAppearingItemAtIndexPath:
initialLayoutAttributesForAppearingSupplementaryElementOfKind:atIndexPath:
initialLayoutAttributesForAppearingDecorationElementOfKind:atIndexPath:
finalLayoutAttributesForDisappearingItemAtIndexPath:
finalLayoutAttributesForDisappearingSupplementaryElementOfKind:atIndexPath:
finalLayoutAttributesForDisappearingDecorationElementOfKind:atIndexPath:
布局间切换
将一个 collection view 布局动态的切换到另外一个布局。setCollectionViewLayout:animated:(突然想到那种四角碰撞,同时形变的小例子)
发现快捷键: option + command = 在IB中动态标尺