在iOS開發中咱們會大量用到UIScrollView這個控件,咱們使用的UITableView/UICollectionView/UITextView都繼承自它。UIScrollView的頻繁使用讓我對它的底層實現產生了興趣,它究竟是如何工做的?如何實現一個UIScrollView?讀完本篇文章,相信你必定也能夠本身實現一個簡易的UIScrollView。源代碼git
這部分請參考我以前的文章——iOS開發筆記(四):frame與bounds的區別詳解github
UIScrollView其實就是bounds.origin != (0,0)的特殊狀況。而ContentOffset、ContentSize和ContentInset做爲UIScrollView三個基本的屬性,其實都是跟origin相關,下面詳細討論。post
ContentOffset是UIScrollView當前顯示區域頂點相對於frame頂點的偏移量,好比你把視圖上拉了100個點,也就是y偏移了100,ContentOffset就是(0,100)。性能
從前文能夠得知,當修改SuperView.bounds.origin時,會變相的修改SubView的實際座標,從而影響SubView在SuperView中的位置。若是咱們修改SuperView.bounds.origin從(0,0)變爲(0,100),SubViews的frame並不會產生變化,產生變化的實際是SubViews的真實座標點。SubViews的真實座標點會減少100點,也就是上移100點,而對應到SubView在視圖的效果就是上移了100個點,也就是ContentOffset爲(0,100)。因此以下:spa
ContentOffset = bounds.origin;
複製代碼
這樣,理解了ContentOffset以後咱們就能夠實現一個簡單的UIScrollView,代碼以下:code
- (void)addGestureAndViews {
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(handlePanGesture:)];
[self.view addGestureRecognizer:pan];
UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 20, 100, 100)];
[view1 setBackgroundColor:[UIColor blueColor]];
[self.view addSubview:view1];
UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(SCREEN_WIDTH - 100, SCREEN_HEIGHT - 100, 100, 100)];
[view2 setBackgroundColor:[UIColor brownColor]];
[self.view addSubview:view2];
}
- (void)handlePanGesture:(UIPanGestureRecognizer *)panGestureRecognizer {
//ContentOffset
CGPoint touchPoint = [panGestureRecognizer translationInView:self.view];//獲取手勢位置
CGFloat newOriginY = self.view.bounds.origin.y - touchPoint.y;//根據手勢位置計算新的origin值
CGFloat newOriginX = self.view.bounds.origin.x - touchPoint.x;
CGRect viewBounds = self.view.bounds;
viewBounds.origin.y = newOriginY;//賦值
viewBounds.origin.x = newOriginX;
self.view.bounds = viewBounds;
[panGestureRecognizer setTranslation:CGPointZero inView:self.view];
}
複製代碼
每當咱們拖動SuperView的時候:cdn
其實,從上面能夠看出來,當咱們拖動的時候,動的並非ScrollView,而是SubViews。blog
ContentSize其實就是UIScrollView能夠滾動的區域,好比frame = (0,0,320,480) ,contentSize = (320,960),表明你的scrollview能夠上下滾動,滾動區域爲frame大小的兩倍。這個東西實際上是抽象的。抽象的目的是爲了讓你們更好地運用UIScrollView,而不用去理解其背後的實現原理(其實就是修改bounds.origin這一點而已)。繼承
看上面的運行圖,小夥伴們會發現,拖動起來無邊無界啊,因而ContentSize橫空出世,其本質就是對bounds.origin的變化約束一個範圍,使其在規定的範圍內拖動。開發
咱們修改代碼以下:
- (void)handlePanGesture:(UIPanGestureRecognizer *)panGestureRecognizer {
//ContentSize
CGPoint touchPoint = [panGestureRecognizer translationInView:self.view];
CGFloat newOriginY = self.view.bounds.origin.y - touchPoint.y;
CGFloat newOriginX = self.view.bounds.origin.x - touchPoint.x;
CGFloat minOriginY = 0.0;
CGFloat minOriginX = 0.0;
CGFloat maxOriginY = 20.0;
CGFloat maxOriginX = 20.0;
CGRect viewBounds = self.view.bounds;
viewBounds.origin.y = fmax(minOriginY, fmin(newOriginY, maxOriginY));//比最大值小的同時比最小值大:min<=newOriginY<=maxOriginY
viewBounds.origin.x = fmax(minOriginX, fmin(newOriginX, maxOriginX));
self.view.bounds = viewBounds;
[panGestureRecognizer setTranslation:CGPointZero inView:self.view];
}
複製代碼
這樣,只要修改minOriginY、minOriginX、maxOriginY、maxOriginX四個值就能肯定UIScrollView的滾動範圍,由此,ContentSize也能推到得出,以下:
ContentSize.height = view.bounds.size.height + maxOriginY;
ContentSize.width = view.bounds.size.width + maxOriginX;
複製代碼
ContentInset是UIScrollView的contentView的頂點相對於UIScrollView的位置,例如你的ContentInset = (0,100),那麼你的contentView就是從UIScrollView的(0,100)開始顯示。
這個屬性可以在UIScrollView的4周增長額外的滾動區域,以此能夠實現下拉刷新,鍵盤彈出的同時擡高View等等。
修改代碼以下:
- (void)handlePanGesture:(UIPanGestureRecognizer *)panGestureRecognizer {
//ContentInset
CGPoint touchPoint = [panGestureRecognizer translationInView:self.view];//獲取手勢位置
CGFloat newOriginY = self.view.bounds.origin.y - touchPoint.y;//根據手勢位置計算新的origin值
CGFloat newOriginX = self.view.bounds.origin.x - touchPoint.x;
CGFloat min = 0.0;
CGFloat maxOriginY = 600.0;
CGFloat maxOriginX = 0;
if (panGestureRecognizer.state == UIGestureRecognizerStateEnded) {
min = 0.0;
maxOriginY = 600.0;
} else {
min = -50.0;
maxOriginY = 650.0;
}
CGRect viewBounds = self.view.bounds;
viewBounds.origin.y = fmax(min, fmin(newOriginY, maxOriginY));//比最大值小的同時比最小值大:min<=newOriginY<=maxOriginY
viewBounds.origin.x = fmax(0, fmin(newOriginX, maxOriginX));
self.view.bounds = viewBounds;
[panGestureRecognizer setTranslation:CGPointZero inView:self.view];
}
複製代碼
因此,ContentInset其實只是在不一樣狀態下修改了maxOriginY、maxOriginX等等的值,從而實現了在不改變ContentSize的狀況下使滾動區域獲得了擴展。
回顧一下,其實ContentOffset、ContentSize、ContentInset仍是Bounces效果本質都是在跟origin玩耍:Offset直接是origin的別稱,而Size、Inset都是修改了origin的改變範圍。可是各個屬性又有本身專有的做用,Size能夠肯定滾動的範圍,而Inset能夠在不修改原有滾動範圍的同時,擴大整體滾動範圍。實現了這三個屬性,也就能實現最基本的UIScrollView。