一、概念理解
Frame:
- 该view在父view坐标系统中的位置和大小(参考系是父view的坐标系)
Bounds:
- 该view在自己坐标系统中的位置和大小(参考系是自己view的坐标系)
- 影响子view的布局
- scrollView的滚动是改变bounds的origin来进行的
contentOffset:
- contentView的顶部和scrollview的顶部的offset是Y值,左边界的差是X值
- 另一种理解方式:bounds的orgin
var contentOffset: CGPoint {
get { return bounds.origin }
set {
var bounds = self.bounds
bounds.origin = newValue
self.bounds = bounds
}
}
contentSize:
- The size of the content view.
contentView包括:
1.cell
2.sectionHeader / sectionFooter
3.tableHeaderView / tableFooterView
contentInset:
- The custom distance that the content view is inset from the safe area or scroll view edges.
- contentInset的会影响contentOffset的最大值和最小值
1、没有inset.top时,contentOffset.y >= 0,当设置inset.top = 88时,contentOffset.y >= -88
2、没有inset.bottom时,contentOffset.y <= (contentView.heigth - scrollView.height),当设置inset.bottom = 40时,contentOffset.y <= (contentView.heigth - scrollView.height + 40)
bounce:
- 根据contentOffset和contentInset我们可以模拟一下bounce的实现代码
var contentOffset: CGPoint {
get { return bounds.origin }
set {
var bounds = self.bounds
if (bounce && !isDragging) || !bounce {
if newValue.y <= -contentInset.top {
newValue.y = -contentOffset.top
}
if newValue.y >=
(contentView.height-frame.height+contentInset.bottom) {
newValue.y = contentView.height-frame.height+contentInset.bottom
}
}
bounds.origin = newValue
self.bounds = bounds
}
}
- 在view上增加一个panGestureRecognizer,当拖拽时isDragging设置为true,将偏移量设置给contentOffset,当停止拖拽时将isDragging设置为false,同时也设置偏移量,如果这个时候的偏移量已经超出了contentSize,设置动画就会产生弹簧效果
上面三个属性的图解:
adjustedContentIndet:
-
Use this property to obtain the adjusted area in which to draw content. The contentInsetAdjustmentBehavior property determines whether the safe area insets are included in the adjustment.** The safe area insets are then added to the values in the contentInset property to obtain the final value of this property**.(文档描述)
-
contentInsetAdjustmentBehavior(ios 11之后)不是never时,系统会在原有的contentInset上加上safeAreaInset,设置inset的时候起作用的也是该属性
- 参考安全区适配
safeAreaInset:
- iPhone X系列,竖屏(top: 44,left: 0, bottom: 34, right: 0),横屏(top: 0,left: 44, bottom: 21, right: 34)
二、实际案例
2.1、计算cell出现在期望区域的时机
- 需求背景:用户主动触发refresh或者loadmore时显示引导tips,refresh后在刷新的第二个cell上显示,loadmore之后在新出现的第二个cell上显示
- loadmore的情况比较复杂需要进行计算,假定是在第12个cell上显示tips,计算contentOffset.y的临界值来判断cell的位置
- 参考位置:第12个cell底部与collectionView的底部平齐,此时contentOffset.y的临界值为
第12个cell完全出现在collectionView上
临界值 = 12 x cell.height - collectionView.frame.size.height
contentOffset.y >= 临界值
- 期望位置:第12个cell的底部与tabBar的顶部平齐,即该cell出现在用户视野中tips显示
第12个cell出现在tabBar以上
临界值 = 12 x cell.height - frame的height + tabBar.heigth
contentOffset.y >= 临界值
- 继续拓展,期望位置:第12个cell向上滚动直至消失在用户视野中(不是需求)
第12个cell正好被navBar遮盖
临界值 = 12 x cell.height - frame的height + (frame的height-navbar.height)
contentOffset.y >= 临界值
2.2、MJRefresh中的应用
- MJHeader:这里写的比源码简单,只表达了header的停留和消失的处理逻辑
// 刷新的时刻
let criticalValue = self.mj_h + originalInset.top // 临界值
let insetT = abs(self.scrollView.mj_offsetY) > criticalValue ?
criticalValue : originalInset.top
if (self.scrollView.mj_insetT != insetT) {
// 增大了inset.top,意味着offsetY的最小值比原来更小
self.scrollView.mj_insetT = insetT
}
// 完成刷新
self.scrollView.mj_insetT = originalInset.top
- MJFooter:和本文的2.1的案例相似,当contentOffset.y >= 临界值时,让footer出现然后变化刷新状态
三、总结
-
contentInset影响contentOffset的最大值和最小值,也可以做到refresh tips停留效果
-
找到contentOffset的临界值可以判断某个cell的显示位置,建议把cell刚好完全出现在collectionView/scrollView的时机作为参考点
-
在使用scrollView的时候建议将系统调整inset设置为never,有我们自己来设置inset