通過(guò)UIKit坐標(biāo)系來(lái)全面掌握iOS中的UIScrollView組件
感謝UIKit的坐標(biāo)系統(tǒng)特性,使我們之花了30幾行代碼就能重現(xiàn)UIScrollView的精華,當(dāng)然真正的UIScrollView要比我們所做的復(fù)雜的多,反彈效果,動(dòng)量滾動(dòng),放大試圖,還有代理方法,這些特性我們沒(méi)有在這里涉及到。
首先,讓我們先來(lái)了解一下UIKit中的坐標(biāo)系是怎么工作的。如果你只對(duì)滾動(dòng)試圖的代碼實(shí)現(xiàn)感興趣可以放心跳過(guò)下一小節(jié)。UIKit坐標(biāo)系每一個(gè)View都定義了他自己的坐標(biāo)系統(tǒng)。如下圖所示,x軸指向右方,y軸指向下方:

注意這個(gè)邏輯坐標(biāo)系并不關(guān)注包含在其中View的寬度和高度。整個(gè)坐標(biāo)系沒(méi)有邊界向四周無(wú)限延伸.我們?cè)谧鴺?biāo)系中放置四個(gè)子View。每一次色塊代表一個(gè)View:

添加View的代碼實(shí)現(xiàn)如下:
UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 100, 100)];
redView.backgroundColor = [UIColor colorWithRed:0.815 green:0.007
blue:0.105 alpha:1];
UIView *greenView = [[UIView alloc] initWithFrame:CGRectMake(150, 160, 150, 200)];
greenView.backgroundColor = [UIColor colorWithRed:0.494 green:0.827
blue:0.129 alpha:1];
UIView *blueView = [[UIView alloc] initWithFrame:CGRectMake(40, 400, 200, 150)];
blueView.backgroundColor = [UIColor colorWithRed:0.29 green:0.564
blue:0.886 alpha:1];
UIView *yellowView = [[UIView alloc] initWithFrame:CGRectMake(100, 600, 180, 150)];
yellowView.backgroundColor = [UIColor colorWithRed:0.972 green:0.905
blue:0.109 alpha:1];
[mainView addSubview:redView];
[mainView addSubview:greenView];
[mainView addSubview:blueView];
[mainView addSubview:yellowView];
bounds
Apple關(guān)于UIView的文檔中是這樣描述bounds屬性的:
bounds矩形…描述了該視圖在其自身坐標(biāo)系中的位置和大小。
一個(gè)View可以被看作是定義在其所在坐標(biāo)系平面上的一扇窗戶或者說(shuō)是一個(gè)矩形的可視區(qū)域。View的邊界表明了這個(gè)矩形可視區(qū)域的位置和大小。
假設(shè)我們的View寬320像素,高480像素,原點(diǎn)在(0,0)。那么這個(gè)View就變成了整個(gè)坐標(biāo)系平面的觀察口,它展示的只是整個(gè)平面的一小部分。位于該View邊界外的區(qū)域依然存在,只是被隱藏起來(lái)了。

一個(gè)View提供了其所在平面的一個(gè)觀察口。View的bounds矩形描述了這個(gè)可是區(qū)域的位置和大小。
Frame
接下來(lái)我們來(lái)試著修改bounds的原點(diǎn)坐標(biāo):
CGRect bounds = mainView.bounds;
bounds.origin = CGPointMake(0, 100);
mainView.bounds = bounds;
當(dāng)我們把bound原點(diǎn)設(shè)為(0,100)后,整個(gè)畫面看起來(lái)就像這樣:

修改bounds的原點(diǎn)就相當(dāng)與在平面上移動(dòng)這個(gè)可視區(qū)域。
看起來(lái)好像是這個(gè)View向下移動(dòng)了100像素,在這個(gè)View自己的坐標(biāo)系中這確實(shí)沒(méi)錯(cuò)。不過(guò)這個(gè)View真正位于屏幕上的位置(更準(zhǔn)確的說(shuō)在其父View上的位置)其實(shí)沒(méi)有改變,因?yàn)檫@是由View的frame屬性決定的,它并沒(méi)有改變:
frame矩形…定義了這個(gè)View在其父View坐標(biāo)系中的位置和大小。
由于View的位置是相對(duì)固定的,你可以把整個(gè)坐標(biāo)平面想象成我們可以上下拖動(dòng)的透明幕布,把這個(gè)View想象成我們觀察坐標(biāo)平面的窗口。調(diào)整View的Bounds屬性就相當(dāng)于拖動(dòng)這個(gè)幕布,那么下方的內(nèi)容就能在我們View中被觀察到:

修改bounds的原點(diǎn)坐標(biāo)也相當(dāng)于把整個(gè)坐標(biāo)系向上拖動(dòng),因?yàn)閂iew的frame沒(méi)由變過(guò),所以它相對(duì)于父View的位置沒(méi)有變化過(guò)。
其實(shí)這就是UIScrollView滑動(dòng)時(shí)所發(fā)生的事情。注意從一個(gè)用戶的角度來(lái)看,他以為時(shí)這個(gè)View中的子View在移動(dòng),其實(shí)他們的在坐標(biāo)系中位置(他們的frame)沒(méi)有發(fā)生過(guò)變化。
打造你的UIScrollView
一個(gè)scroll view并不需要其中子View的坐標(biāo)來(lái)使他們滾動(dòng)。唯一要做的就是改變他的bounds屬性。知道了這一點(diǎn),實(shí)現(xiàn)一個(gè)簡(jiǎn)單的scroll view就沒(méi)什么困難了。我們用一個(gè)gesture recognizer來(lái)識(shí)別用戶的拖動(dòng)操作,根據(jù)用戶拖動(dòng)的偏移量來(lái)改變bounds的原點(diǎn):
// CustomScrollView.h
@import UIKit;
@interface CustomScrollView : UIView
@property (nonatomic) CGSize contentSize;
@end
// CustomScrollView.m
#import "CustomScrollView.h"
@implementation CustomScrollView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self == nil) {
return nil;
}
UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:@selector(handlePanGesture:)];
[self addGestureRecognizer:gestureRecognizer];
return self;
}
- (void)handlePanGesture:(UIPanGestureRecognizer *)gestureRecognizer
{
CGPoint translation = [gestureRecognizer translationInView:self];
CGRect bounds = self.bounds;
// Translate the view's bounds, but do not permit values that would violate contentSize
CGFloat newBoundsOriginX = bounds.origin.x - translation.x;
CGFloat minBoundsOriginX = 0.0;
CGFloat maxBoundsOriginX = self.contentSize.width - bounds.size.width;
bounds.origin.x = fmax(minBoundsOriginX, fmin(newBoundsOriginX, maxBoundsOriginX));
CGFloat newBoundsOriginY = bounds.origin.y - translation.y;
CGFloat minBoundsOriginY = 0.0;
CGFloat maxBoundsOriginY = self.contentSize.height - bounds.size.height;
bounds.origin.y = fmax(minBoundsOriginY, fmin(newBoundsOriginY, maxBoundsOriginY));
self.bounds = bounds;
[gestureRecognizer setTranslation:CGPointZero inView:self];
}
@end
和真正的UIScrollView一樣,我們的類也有一個(gè)contentSize屬性,你必須從外部來(lái)設(shè)置這個(gè)值來(lái)指定可以滾動(dòng)的區(qū)域,當(dāng)我們改變bounds的大小時(shí)我們要確保設(shè)置的值是有效的。
結(jié)果:

UIScrollView常用操作方法整理

- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
// 創(chuàng)建一個(gè)滾動(dòng)視圖
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 64, 320, 568-64)];
// 設(shè)置代理
self.scrollView.delegate = self;
self.scrollView.backgroundColor = [UIColor redColor];
[self.view addSubview:_scrollView];
/**
* CGPoint contentOffSet 監(jiān)控目前滾動(dòng)的位置
* CGSize contentSize 滾動(dòng)范圍大小(主屬性)
* UIEdgeInsets contentInset 視圖在scrollView中的位置
* BOOL directionalLockEnabled 指定控件是否只能在一個(gè)方向上滾動(dòng)
* BOOL bounces 控制控件遇到邊框是否反彈
* BOOL alwaysBounceVertical 控制垂直方向遇到邊框是否反彈
* BOOL alwaysBounceHorizontal 控制水平方向遇到邊框是否反彈
* BOOL pagingEnabled 控制控件是否整頁(yè)翻動(dòng)
* BOOL scrollEnabled 控制控件是否能滾動(dòng)
* BOOL showsHorizontalScrollIndicator 控制是否顯示水平方向的滾動(dòng)條
* BOOL showsVerticalScrollIndicator 控制是否顯示垂直方向的滾動(dòng)條
* UIEdgeInsets scrollIndicatorInsets 指定滾動(dòng)條在scrollerView中的位置
* UIScrollViewIndicatorStyle indicatorStyle 設(shè)定滾動(dòng)條的樣式
* float decelerationRate 改變scrollerView的減速點(diǎn)位置
* BOOL tracking 監(jiān)控當(dāng)前目標(biāo)是否正在被跟蹤
* BOOL dragging 監(jiān)控當(dāng)前目標(biāo)是否正在被拖拽
* BOOL decelerating 監(jiān)控當(dāng)前目標(biāo)是否正在減速
* BOOL delaysContentTouches 控制視圖是否延時(shí)調(diào)用開(kāi)始滾動(dòng)的方法
* BOOL canCancelContentTouches 控制控件是否接觸取消touch的事件
* float minimumZoomScale 縮放的最小比例
* float maximumZoomScale 縮放的最大比例
* float zoomScale 設(shè)置變化比例
* BOOL bouncesZoom 控制縮放的時(shí)候是否會(huì)反彈
* BOOL zooming 判斷控件的大小是否正在改變
* BOOL zoomBouncing 判斷是否正在進(jìn)行縮放反彈
* BOOL scrollsToTop 控制控件滾動(dòng)到頂部
*/
// 提示用戶,在界面創(chuàng)建的時(shí)候,水平滾動(dòng)條或者垂直滾動(dòng)條會(huì)出現(xiàn)一次閃現(xiàn)效果
[self.scrollView flashScrollIndicators];
// 偏移帶動(dòng)畫效果
[self.scrollView setContentOffset:CGPointMake(320, 0) animated:YES];
}
#pragma mark UIScrollViewDelegate
// 只要滾動(dòng)了就會(huì)觸發(fā)
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
{
}
// 開(kāi)始拖拽視圖
// 當(dāng)開(kāi)始滾動(dòng)視圖時(shí),執(zhí)行該方法。一次有效滑動(dòng)(開(kāi)始滑動(dòng),滑動(dòng)一小段距離,只要手指不松開(kāi),只算一次滑動(dòng)),只執(zhí)行一次。
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
{
}
// 滑動(dòng)視圖,當(dāng)手指離開(kāi)屏幕那一霎那,調(diào)用該方法。一次有效滑動(dòng),只執(zhí)行一次。
// decelerate,指代,當(dāng)我們手指離開(kāi)那一瞬后,視圖是否還將繼續(xù)向前滾動(dòng)(一段距離),經(jīng)過(guò)測(cè)試,decelerate=YES
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
{
}
// 將開(kāi)始降速時(shí)
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView;
{
}
// 滾動(dòng)視圖減速完成,滾動(dòng)將停止時(shí),調(diào)用該方法。一次有效滑動(dòng),只執(zhí)行一次。
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
{
}
// 滾動(dòng)動(dòng)畫停止時(shí)執(zhí)行,代碼改變時(shí)出發(fā),也就是setContentOffset改變時(shí)
// 當(dāng)滾動(dòng)視圖動(dòng)畫完成后,調(diào)用該方法,如果沒(méi)有動(dòng)畫,那么該方法將不被調(diào)用
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView;
{
}
// 設(shè)置放大縮小的視圖,要是uiscrollview的subview , 返回將要縮放的UIView對(duì)象。要執(zhí)行多次
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView;
{
return nil;
}
// 當(dāng)將要開(kāi)始縮放時(shí),執(zhí)行該方法。一次有效縮放,就只執(zhí)行一次。
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
{
NSLog(@"scrollViewWillBeginZooming");
}
// 當(dāng)縮放結(jié)束后,并且縮放大小回到minimumZoomScale與maximumZoomScale之間后(我們也許會(huì)超出縮放范圍),調(diào)用該方法。
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(double)scale;
{
}
// 如果你不是完全滾動(dòng)到滾軸視圖的頂部,你可以輕點(diǎn)狀態(tài)欄,那個(gè)可視的滾軸視圖會(huì)一直滾動(dòng)到頂部,那是默認(rèn)行為,你可以通過(guò)該方法返回NO來(lái)關(guān)閉它
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView;
{
return YES;
}
// 已經(jīng)滑動(dòng)到頂部
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView;
{
}
相關(guān)文章
iOS開(kāi)發(fā)中的幾個(gè)手勢(shì)操作實(shí)例分享
這篇文章主要介紹了iOS開(kāi)發(fā)中的幾個(gè)手勢(shì)操作實(shí)例分享,編寫代碼為傳統(tǒng)的Objective-C,需要的朋友可以參考下2015-09-09
iOS滑動(dòng)解鎖、滑動(dòng)獲取驗(yàn)證碼效果的實(shí)現(xiàn)代碼
這篇文章主要介紹了iOS滑動(dòng)解鎖、滑動(dòng)獲取驗(yàn)證碼效果的實(shí)現(xiàn)代碼,小編認(rèn)為還不錯(cuò),非常實(shí)用,所有分享給大家,感興趣的朋友可以參考下2016-05-05
IOS 解決UIButton 點(diǎn)擊卡頓/延遲的問(wèn)題
本文主要介紹 IOS UIButton, 這里給大家提供代碼實(shí)例作為參考,解決 UIButton 點(diǎn)擊卡頓或者延遲問(wèn)題,在開(kāi)發(fā) IOS 項(xiàng)目的小伙伴如果遇到這樣的問(wèn)題可以參考下2016-07-07
iOS使用UIBezierPath實(shí)現(xiàn)ProgressView
這篇文章主要為大家詳細(xì)介紹了iOS使用UIBezierPath實(shí)現(xiàn)ProgressView,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
iOS實(shí)現(xiàn)UITableView左滑刪除復(fù)制即用功能
這篇文章主要介紹了iOS實(shí)現(xiàn)UITableView左滑刪除復(fù)制即用功能,在項(xiàng)目開(kāi)發(fā)中經(jīng)常會(huì)用到這樣的需求,下面小編把實(shí)現(xiàn)代碼分享給大家,需要的朋友可以參考下2017-09-09
IOS 開(kāi)發(fā)之UIView動(dòng)畫的實(shí)例詳解
這篇文章主要介紹了IOS 開(kāi)發(fā)之UIView動(dòng)畫的實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-07-07
iOS開(kāi)發(fā)之銀行卡號(hào)識(shí)別
本文給大家分享ios開(kāi)發(fā)之銀行卡號(hào)識(shí)別功能,思路明確,需要的朋友參考下吧2016-12-12
iOS內(nèi)存錯(cuò)誤EXC_BAD_ACCESS的解決方法
iOS開(kāi)發(fā),最郁悶的莫過(guò)于程序毫無(wú)征兆地就崩潰了,用bt命令打出調(diào)用棧,給出的是一堆系統(tǒng)EXC_BAD_ACCESS的信息,根本沒(méi)辦法定位問(wèn)題出現(xiàn)在哪里2013-06-06

