一. UIScrollView屬性、方法和代理
1. UIScrollView屬性、方法
自定義系統(tǒng)的UIScrollView之前我們要先解釋UIScrollView的屬性、方法,代理方法,因?yàn)閁IScrollView的有些屬性還是比較難理解的,直接拷貝系統(tǒng)UIScrollView.h文件一個(gè)一個(gè)解釋,如下:
contentOffset,contentSize,contentInset
@property(nonatomic) CGPoint contentOffset; // default CGPointZero
@property(nonatomic) CGSize contentSize; // default CGSizeZero
@property(nonatomic) UIEdgeInsets contentInset; // default UIEdgeInsetsZero. add additional scroll area around content
關(guān)于這三個(gè)屬性,網(wǎng)上有太多的解釋,可參考:http://m.itdecent.cn/p/e5582fe5dd4a
下面補(bǔ)充一下,iOS7之后出現(xiàn)了下面這個(gè)屬性,這個(gè)屬性是UIViewController的,如下:
automaticallyAdjustsScrollViewInsets
@property(nonatomic,assign) BOOL automaticallyAdjustsScrollViewInsets API_DEPRECATED("Use UIScrollView's contentInsetAdjustmentBehavior instead", ios(7.0,11.0),tvos(7.0,11.0)); // Defaults to YES
這個(gè)屬性的字面意思是自動(dòng)調(diào)節(jié)ScrollView的Insets,默認(rèn)YES,比如我們像如下創(chuàng)建scrollView的時(shí)候:
scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
- 因?yàn)閍utomaticallyAdjustsScrollViewInsets默認(rèn)是YES,系統(tǒng)幫我們調(diào)節(jié)了ScrollVIew的Insets,所以我們的scrollView是從導(dǎo)航欄下面開始的,顯示正常。
- 如果我們把a(bǔ)utomaticallyAdjustsScrollViewInsets設(shè)置為NO,我們就要手動(dòng)設(shè)置ScrollVIew的Insets,代碼如下:
//其中88是導(dǎo)航欄的高度
scrollView.contentInset = UIEdgeInsetsMake(88.f, 0.f, 0.f, 0.f);
scrollView.contentOffset = CGPointMake(0.f, -88.f);//這句如果不寫,不會(huì)自動(dòng)滾動(dòng)到指定位置
- 如果我們把a(bǔ)utomaticallyAdjustsScrollViewInsets設(shè)置為NO,又沒手動(dòng)設(shè)置ScrollVIew的Insets,這時(shí)候就需要調(diào)節(jié)scrollView的frame的y值了。
這個(gè)屬性在iOS11已廢棄,使用UIScrollView的如下屬性代替,和上面屬性一樣的,默認(rèn)自動(dòng)調(diào)節(jié),可以設(shè)置為Never
contentInsetAdjustmentBehavior
/* Configure the behavior of adjustedContentInset.
Default is UIScrollViewContentInsetAdjustmentAutomatic.
*/
@property(nonatomic) UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior API_AVAILABLE(ios(11.0),tvos(11.0));
下面再介紹一個(gè)屬性
safeAreaInsets
@property (nonatomic,readonly) UIEdgeInsets safeAreaInsets API_AVAILABLE(ios(11.0),tvos(11.0));
iOS11之后,由于劉海屏幕的出現(xiàn)有了這個(gè)屬性,它是安全區(qū)域之外的額外inserts,比如iPhone X豎屏?xí)r占滿整個(gè)屏幕的控制器的view的safeAreaInsets是(44,0,34,0) ,44和34分別是劉海和手勢(shì)操作的區(qū)域。
說了這么多介紹下面這個(gè)屬性就很簡(jiǎn)單了
adjustedContentInset
/* When contentInsetAdjustmentBehavior allows, UIScrollView may incorporate
its safeAreaInsets into the adjustedContentInset.
*/
@property(nonatomic, readonly) UIEdgeInsets adjustedContentInset API_AVAILABLE(ios(11.0),tvos(11.0));
正如注釋所寫:
- 當(dāng)設(shè)置UIScrollViewContentInsetAdjustmentAutomatic時(shí):
adjustedContentInset = safecontentInset + scrollView.contentInset - 當(dāng)設(shè)置UIScrollViewContentInsetAdjustmentNever時(shí):
adjustedContentInset = scrollView.contentInset
adjustedContentInsetDidChange方法
/* Also see -scrollViewDidChangeAdjustedContentInset: in the UIScrollViewDelegate protocol.
*/
- (void)adjustedContentInsetDidChange API_AVAILABLE(ios(11.0),tvos(11.0)) NS_REQUIRES_SUPER;
adjustedContentInset屬性改變會(huì)調(diào)用,和代理里面scrollViewDidChangeAdjustedContentInset方法一樣的。
contentLayoutGuide和frameLayoutGuide
iOS11新增,用于描述內(nèi)容布局和整體布局信息
@property(nonatomic,readonly,strong) UILayoutGuide *contentLayoutGuide API_AVAILABLE(ios(11.0),tvos(11.0));
@property(nonatomic,readonly,strong) UILayoutGuide *frameLayoutGuide API_AVAILABLE(ios(11.0),tvos(11.0));
屬性
更多屬性的解釋可見注釋,如下:
//代理
@property(nullable,nonatomic,weak) id<UIScrollViewDelegate> delegate; // default nil. weak reference
//默認(rèn)為FALSE, 如果設(shè)置為TRUE,那么在推拖拽UIScrollView的時(shí)候,會(huì)鎖住水平或豎直方向的滑動(dòng)
@property(nonatomic,getter=isDirectionalLockEnabled) BOOL directionalLockEnabled; // default NO. if YES, try to lock vertical or horizontal scrolling while dragging
//是否有回彈效果
@property(nonatomic) BOOL bounces; // default YES. if YES, bounces past edge of content and back again
//alwaysBounceVertical 豎直方向總是可以彈性滑動(dòng),默認(rèn)是NO, 當(dāng)設(shè)置為TRUE(前提是屬性bounces必須為TRUE)的時(shí)候,即使contentSize設(shè)置的width 和 height都比UIScrollView的width 和 height小,在垂直方向上都可以有滑動(dòng)效果,甚至即使我們不設(shè)置contentSize都可以產(chǎn)生滑動(dòng)效果; 反之,如果設(shè)置alwaysBounceVertical為FALSE, 那么當(dāng)contentSize設(shè)置的width 和 height都比UIScrollView的width 和 height小的時(shí)候,即使bounces設(shè)置為TRUE,那么不可能產(chǎn)生彈性效果
@property(nonatomic) BOOL alwaysBounceVertical; // default NO. if YES and bounces is YES, even if content is smaller than bounds, allow drag vertically
//同上
@property(nonatomic) BOOL alwaysBounceHorizontal; // default NO. if YES and bounces is YES, even if content is smaller than bounds, allow drag horizontally
//是否可分頁(yè),默認(rèn)是FALSE, 如果設(shè)置成TRUE, 則可分頁(yè)
@property(nonatomic,getter=isPagingEnabled) BOOL pagingEnabled __TVOS_PROHIBITED;// default NO. if YES, stop on multiples of view bounds
//是否可以滾動(dòng)
@property(nonatomic,getter=isScrollEnabled) BOOL scrollEnabled; // default YES. turn off any dragging temporarily
//是否展示水平方向滾動(dòng)條
@property(nonatomic) BOOL showsHorizontalScrollIndicator; // default YES. show indicator while we are tracking. fades out after tracking
//是否展示垂直方向滾動(dòng)條
@property(nonatomic) BOOL showsVerticalScrollIndicator; // default YES. show indicator while we are tracking. fades out after tracking
//滑動(dòng)條的邊緣插入,即是距離上、左、下、右的距離
//例如:testScrollView.scrollIndicatorInsets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
//當(dāng)向下滑動(dòng)時(shí),滑動(dòng)條距離頂部的距離總是20
@property(nonatomic) UIEdgeInsets scrollIndicatorInsets; // default is UIEdgeInsetsZero. adjust indicators inside of insets
//滾動(dòng)條樣式,是個(gè)枚舉類型:
@property(nonatomic) UIScrollViewIndicatorStyle indicatorStyle; // default is UIScrollViewIndicatorStyleDefault
//減速率,CGFloat類型,當(dāng)你滑動(dòng)松開手指后的減速速率, 但是盡管decelerationRate是一個(gè)CGFloat類型,但是目前系統(tǒng)只支持以下兩種速率設(shè)置選擇:
UIScrollViewDecelerationRateNormal 值是 0.998
UIScrollViewDecelerationRateFast 值是 0.99
@property(nonatomic) UIScrollViewDecelerationRate decelerationRate NS_AVAILABLE_IOS(3_0);
//索引展示模式,是個(gè)枚舉 : 自動(dòng)顯示或隱藏 || 一直隱藏
@property(nonatomic) UIScrollViewIndexDisplayMode indexDisplayMode API_AVAILABLE(tvos(10.2));
方法
//帶有動(dòng)畫效果的設(shè)置偏移量
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; // animate at constant velocity to new offset
//滑動(dòng)到指定的可見區(qū)域(帶動(dòng)畫),意思就是滑動(dòng)到CGRect所組成的矩形區(qū)域,使其可見. 如已可見則什么都不做
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated; // scroll so rect is just visible (nearest edges). nothing if rect completely visible
// 設(shè)置的時(shí)候,當(dāng)頁(yè)面加載成功出現(xiàn)時(shí),滑動(dòng)條會(huì)自動(dòng)顯示出來,停留一下又自動(dòng)隱藏
// 不設(shè)置的話,頁(yè)面出現(xiàn)時(shí)也不會(huì)顯示滑動(dòng)條,只有在滑動(dòng)過程中會(huì)顯示滑動(dòng)條
- (void)flashScrollIndicators; // displays the scroll indicators for a short time. This should be done whenever you bring the scroll view to front.
屬性
//是否一直追蹤
//返回YES表示用戶手指一直接觸著scrollView(包括手指一直拖動(dòng)scrollView)沒有松開 返回NO表示手指離開scrollView 此時(shí)scrollView做自由滾動(dòng)
@property(nonatomic,readonly,getter=isTracking) BOOL tracking; // returns YES if user has touched. may not yet have started dragging
//是否正在拖動(dòng)
@property(nonatomic,readonly,getter=isDragging) BOOL dragging; // returns YES if user has started scrolling. this may require some time and or distance to move to initiate dragging
//是否正在減速
@property(nonatomic,readonly,getter=isDecelerating) BOOL decelerating; // returns YES if user isn't dragging (touch up) but scroll view is still moving
delaysContentTouches和canCancelContentTouches
@property(nonatomic) BOOL delaysContentTouches; // default is YES. if NO, we immediately call -touchesShouldBegin:withEvent:inContentView:. this has no effect on presses
@property(nonatomic) BOOL canCancelContentTouches; // default is YES. if NO, then once we start tracking, we don't try to drag if the touch moves. this has no effect on presses
關(guān)于這兩個(gè)屬性暫時(shí)只說一點(diǎn),當(dāng)scrollView上面添加一個(gè)UISliderView的時(shí)候,如果拖動(dòng)UISliderView,scrollView會(huì)移動(dòng),當(dāng)我們?cè)O(shè)置delaysContentTouches = NO,canCancelContentTouches = NO的時(shí)候,UISliderView和scrollView的拖動(dòng)才互不影響。
更多詳情參考:http://m.itdecent.cn/p/2c74b7a6c082
touchesShouldBegin和touchesShouldCancelInContentView
//必須子類化之后重寫,用以控制將觸摸事件傳遞給scrollView的子視圖
//在觸摸被傳遞到scrollView的子視圖之前調(diào)用。如果它返回NO,觸摸將不會(huì)被傳遞到子視圖
//這對(duì)press沒有影響
//默認(rèn)返回YES
- (BOOL)touchesShouldBegin:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event inContentView:(UIView *)view;
//如果觸摸已經(jīng)傳遞到滾動(dòng)視圖的子視圖,則在滾動(dòng)開始之前調(diào)用。如果返回NO,觸摸將繼續(xù)傳遞到子視圖,滾動(dòng)將不會(huì)發(fā)生
//如果canCancelContentTouches為NO,則不調(diào)用。如果視圖不是UIControl,默認(rèn)返回YES
- (BOOL)touchesShouldCancelInContentView:(UIView *)view;
關(guān)于動(dòng)畫的一些屬性:
minimumZoomScale和maximumZoomScale
//滑動(dòng)視圖的最小縮放倍數(shù),默認(rèn)是1.0
@property(nonatomic) CGFloat minimumZoomScale; // default is 1.0
//滑動(dòng)視圖的最大縮放倍數(shù),默認(rèn)是1.0(要使得縮放有效果,maximumZoomScale必須要大于minimumZoomScale)
@property(nonatomic) CGFloat maximumZoomScale; // default is 1.0. must be > minimum zoom scale to enable zooming
例如,實(shí)現(xiàn)如下的縮放效果:

代碼如下:
class ViewController: UIViewController, UIScrollViewDelegate {
private var testScrollView: UIScrollView!
private var testImgView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
testScrollView = UIScrollView(frame: CGRect(x: 50, y: 264, width: UIScreen.main.bounds.width - 100, height: 200))
testScrollView.contentSize = CGSize(width: UIScreen.main.bounds.width - 100 , height: 200)
testScrollView.delegate = self
testScrollView.backgroundColor = UIColor.orange
testScrollView.minimumZoomScale = 0.5
testScrollView.maximumZoomScale = 2
view.addSubview(testScrollView)
testImgView = UIImageView(frame: testScrollView.bounds)
testImgView.image = #imageLiteral(resourceName: "testimage2.jpg")
testScrollView.addSubview(testImgView)
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return testImgView
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
let offSetX = scrollView.bounds.width > scrollView.contentSize.width ? (scrollView.bounds.width - scrollView.contentSize.width) * 0.5 : 0.0
let offSetY = scrollView.bounds.height > scrollView.contentSize.height ? (scrollView.bounds.height - scrollView.contentSize.height) * 0.5 : 0.0
testImgView.center = CGPoint(x: scrollView.contentSize.width * 0.5 + offSetX, y: scrollView.contentSize.height * 0.5 + offSetY)
}
}
注意:想要實(shí)現(xiàn)縮放效果,代理必須要實(shí)現(xiàn)func viewForZooming(in scrollView: UIScrollView) -> UIView?方法,否則無(wú)法實(shí)現(xiàn)縮放功能。如果要達(dá)到縮放后的一些效果操作還要實(shí)現(xiàn)代理的func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat)方法。
zoomScale
//當(dāng)前的縮放比例, 默認(rèn)是1.0
@property(nonatomic) CGFloat zoomScale NS_AVAILABLE_IOS(3_0); // default is 1.0
比如,雙擊當(dāng)前圖片,使其縮放成原來的0.8倍:
func tap() {
// 雙擊當(dāng)前圖片,使其縮放成原來的0.8倍
testScrollView.zoomScale = 0.8
// 使圖片居中
let offSetX = testScrollView.bounds.width > testScrollView.contentSize.width ? (testScrollView.bounds.width - testScrollView.contentSize.width) * 0.5 : 0.0
let offSetY = testScrollView.bounds.height > testScrollView.contentSize.height ? (testScrollView.bounds.height - testScrollView.contentSize.height) * 0.5 : 0.0
testImgView.center = CGPoint(x: testScrollView.contentSize.width * 0.5 + offSetX, y: testScrollView.contentSize.height * 0.5 + offSetY)
}
其他動(dòng)畫屬性
//設(shè)置縮放比例,帶動(dòng)畫
- (void)setZoomScale:(CGFloat)scale animated:(BOOL)animated NS_AVAILABLE_IOS(3_0);
//把從scrollView里截取的矩形區(qū)域縮放到整個(gè)scrollView當(dāng)前可視的frame里面。如果截取的區(qū)域大于scrollView的frame時(shí),圖片縮小,如果截取區(qū)域小于frame,會(huì)看到圖片放大。一般情況下rect需要自己計(jì)算出來。即要把用戶點(diǎn)擊坐標(biāo)附近的區(qū)域內(nèi)容在scrollViewl里進(jìn)行縮放
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated NS_AVAILABLE_IOS(3_0);
//彈性縮放,默認(rèn)是true, 設(shè)置成false的話,當(dāng)縮放到最大或最小值的時(shí)候不會(huì)有彈性效果
@property(nonatomic) BOOL bouncesZoom; // default is YES. if set, user can go past min/max zoom while gesturing and the zoom will animate to the min/max value at gesture end
//get屬性,是否正在縮放
@property(nonatomic,readonly,getter=isZooming) BOOL zooming; // returns YES if user in zoom gesture
//get屬性,當(dāng)沒有設(shè)置bouncesZoom的時(shí)候,如果正在縮放過程中則為false,如果縮放到最小值或者最大值時(shí)松開手指則為true; 當(dāng)設(shè)置bouncesZoom = false的時(shí)候,如果正在縮放過程中zoomScale > 1時(shí)則為false,并且縮放到最大值時(shí)松開手指也是false。
@property(nonatomic,readonly,getter=isZoomBouncing) BOOL zoomBouncing; // returns YES if we are in the middle of zooming back to the min/max value
//滑動(dòng)到頂部,默認(rèn)是true,當(dāng)點(diǎn)擊狀態(tài)欄的時(shí)候,如果當(dāng)前UIScrollView不是處在頂部位置,那么可以直接回到頂部;如果已經(jīng)在頂部,則沒有作用;另外必須注意如果要這個(gè)屬性起作用,它的delegate方法func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool不能返回false,否則沒用。
@property(nonatomic) BOOL scrollsToTop __TVOS_PROHIBITED; // default is YES.
//平移手勢(shì),get屬性,這個(gè)手勢(shì)的代理系統(tǒng)設(shè)置的scrollView,不能修改為其他的. 可以通過設(shè)置平移手勢(shì)的屬性來改變平移方式,比如設(shè)置觸摸手指的最少個(gè)數(shù)minimumNumberOfTouches
@property(nonatomic, readonly) UIPanGestureRecognizer *panGestureRecognizer NS_AVAILABLE_IOS(5_0);
//捏合手勢(shì),也就是縮放手勢(shì),get屬性,設(shè)置同平移手勢(shì)一樣,當(dāng)縮放禁用時(shí)返回nil。
@property(nullable, nonatomic, readonly) UIPinchGestureRecognizer *pinchGestureRecognizer NS_AVAILABLE_IOS(5_0);
// 定向按壓手勢(shì),給tvos用的
@property(nonatomic, readonly) UIGestureRecognizer *directionalPressGestureRecognizer ;
//鍵盤消失模式, 默認(rèn)是none,是個(gè)枚舉值
public enum UIScrollViewKeyboardDismissMode : Int {
case none // 無(wú)
case onDrag // 拖拽,只要滑動(dòng)UIScrollView,鍵盤消失
case interactive // 交互式,拖住UIScrollView一直下滑,當(dāng)接觸到鍵盤時(shí),鍵盤就跟著同步下滑
}
@property(nonatomic) UIScrollViewKeyboardDismissMode keyboardDismissMode NS_AVAILABLE_IOS(7_0); // default is UIScrollViewKeyboardDismissModeNone
//系統(tǒng)的刷新控件,就是一個(gè)菊花
@property (nonatomic, strong, nullable) UIRefreshControl *refreshControl NS_AVAILABLE_IOS(10_0) __TVOS_PROHIBITED;
更多關(guān)于圖片縮放,請(qǐng)參考:iOS圖片縮放
2. UIScrollView代理方法
//已經(jīng)滑動(dòng),常用來處理一些偏移量,判斷拖拽狀態(tài)等
- (void)scrollViewDidScroll:(UIScrollView *)scrollView; // any offset changes
//已經(jīng)縮放,處理一些縮放操作
- (void)scrollViewDidZoom:(UIScrollView *)scrollView NS_AVAILABLE_IOS(3_2); // any zoom scale changes
//即將開始拖拽(或許要拖拽移動(dòng)一小段距離才會(huì)調(diào)用),只要一開始拖拽就會(huì)調(diào)用
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
//即將松開手指結(jié)束拖拽
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset NS_AVAILABLE_IOS(5_0);
//已經(jīng)松開手指,結(jié)束拖拽狀態(tài)
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
//即將開始減速
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView; // called on finger up as we are moving
//已經(jīng)結(jié)束減速, 當(dāng)UIScrollView停止時(shí)就調(diào)用
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; // called when scroll view grinds to a halt
//已經(jīng)結(jié)束滑動(dòng)動(dòng)畫,這個(gè)方法起作用的前提是設(shè)置了下滿面提到的兩種方法中的任意一種,否則不會(huì)起作用!
// func setContentOffset(_ contentOffset: CGPoint, animated:
// func scrollRectToVisible(_ rect: CGRect, animated: Bool)
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView; // called when setContentOffset/scrollRectVisible:animated: finishes. not called if not animating
//返回一個(gè)需要縮放的視圖,需要做縮放的時(shí)候必須調(diào)用此方法,之前已經(jīng)講過,不再贅述!
- (nullable UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView; // return a view that will be scaled. if delegate returns nil, nothing happens
//即將開始縮放,在滑動(dòng)視圖開始縮放它的內(nèi)容視圖前調(diào)用
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(nullable UIView *)view NS_AVAILABLE_IOS(3_2); // called before the scroll view begins zooming its content
//已經(jīng)結(jié)束縮放狀態(tài),結(jié)束縮放手勢(shì)時(shí)調(diào)用,在最小和最大值之前進(jìn)行縮放
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(nullable UIView *)view atScale:(CGFloat)scale; // scale between minimum and maximum. called after any 'bounce' animations
//滑到頂部,這個(gè)方法和屬性scrollsToTop用法一致,本質(zhì)一致,返回true則是可以滑動(dòng)頂部,false反之!
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView; // return a yes if you want to scroll to the top. if not defined, assumes YES
//已經(jīng)滑到頂部,當(dāng)滑動(dòng)到頂部的動(dòng)畫完成的時(shí)候調(diào)用
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView; // called when scrolling animation finished. may be called immediately if already at top
//adjustedContentInset屬性發(fā)生改變調(diào)用, 和[UIScrollView adjustedContentInsetDidChange]一樣,當(dāng)然這個(gè)代理方法是給外界用的
- (void)scrollViewDidChangeAdjustedContentInset:(UIScrollView *)scrollView API_AVAILABLE(ios(11.0), tvos(11.0));
二. UIScrollView原理和自定義UIScrollView
1. UIScrollView原理
- 通過上面的屬性介紹,我們發(fā)現(xiàn)系統(tǒng)的UIScrollView有一個(gè)pan手勢(shì),pan手勢(shì)的代理是UIScrollView,并且不能修改pan手勢(shì)的代理。
- 到底UIScrollView是怎么滑動(dòng)的呢?
UIScrollView繼承于UIView,然后自己上面添加一個(gè)手勢(shì),UIScrollView實(shí)質(zhì)就是根據(jù)手勢(shì)的滑動(dòng)修改它的bounds來進(jìn)行view的滑動(dòng),可以在代理方法里打印ScrollView的bounds值來驗(yàn)證。
2. 自定義UIScrollView
系統(tǒng)的UIScrollView特點(diǎn):
首先看一下系統(tǒng)的UIScrollView的一個(gè)特點(diǎn),如下代碼:
//灰色的scrollView添加到紅色的scrollView上
//系統(tǒng)UIScrollView現(xiàn)實(shí)情況是,可以滑動(dòng)到父scrollView上
UIScrollView *scrollViewOne = [[UIScrollView alloc] initWithFrame:CGRectMake(0.f, 100.f, self.view.frame.size.width, 300.f)];
scrollViewOne.backgroundColor = [UIColor redColor];
scrollViewOne.contentSize = CGSizeMake(self.view.frame.size.width*2, 300.f);
UIScrollView *scrollViewTwo = [[UIScrollView alloc] initWithFrame:CGRectMake(0.f, 0.f, self.view.frame.size.width, 300.f)];
scrollViewTwo.contentSize = CGSizeMake(self.view.frame.size.width*2, 300.f);
scrollViewTwo.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:scrollViewOne];
[scrollViewOne addSubview:scrollViewTwo];
紅色的scrollView上面添加一個(gè)灰色的scrollView,灰色的scrollView滑完之后可以滑動(dòng)到紅色的scrollView上。
效果圖如下:
按照我們iOS手勢(shì)手勢(shì)被識(shí)別之后是不會(huì)再傳給父控件了,但是這是怎么回事呢?
我們先屏蔽系統(tǒng)的這個(gè)特點(diǎn),新建一個(gè)EOCScrollView類繼承于UIScrollView,重寫以下方法:
EOCScrollView.m代碼
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
self.panGestureRecognizer.delegate = self;
}
return self;
}
#pragma mark - 重寫手勢(shì)代理,如果是右滑,則禁用掉mainScrollView自帶的
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gestureRecognizer;
CGFloat pointX = [pan translationInView:self].x;
if (pointX < 0 && self.contentOffset.x == self.contentSize.width-self.frame.size.width) {
return NO;
}
return YES;
}
ViewController.m里面代碼如下
EOCScrollView *scrollViewOne = [[EOCScrollView alloc] initWithFrame:CGRectMake(0.f, 100.f, self.view.frame.size.width, 300.f)];
scrollViewOne.backgroundColor = [UIColor redColor];
scrollViewOne.contentSize = CGSizeMake(self.view.frame.size.width*2, 300.f);
EOCScrollView *scrollViewTwo = [[EOCScrollView alloc] initWithFrame:CGRectMake(0.f, 0.f, self.view.frame.size.width, 300.f)];
scrollViewTwo.contentSize = CGSizeMake(self.view.frame.size.width*2, 600.f);
scrollViewTwo.backgroundColor = [UIColor lightGrayColor];
EOCScrollView *scrollViewThree = [[EOCScrollView alloc] initWithFrame:CGRectMake(0.f, 0.f, self.view.frame.size.width, 300.f)];
scrollViewThree.contentSize = CGSizeMake(self.view.frame.size.width*2, 300.f);
scrollViewThree.backgroundColor = [UIColor yellowColor];
[self.view addSubview:scrollViewOne];
[scrollViewOne addSubview:scrollViewTwo];
[scrollViewTwo addSubview:scrollViewThree];
這樣我們發(fā)現(xiàn)黃色scrollViewThree滑完之后也不會(huì)滑到scrollViewTwo上面了。
① 自定義ScrollView
下面我們就自定義一個(gè)scrollView,默認(rèn)是沒有如上特性的,我們實(shí)現(xiàn)如上特性
分兩步:
自定義一個(gè)EOCCustomScrollView繼承于UIView
代碼如下:
EOCCustomScrollView.h文件
#import <UIKit/UIKit.h>
#import "EOCPanGestureOne.h"
@interface EOCCustomScrollView : UIView
@property(nonatomic, assign)CGSize contentSize;
@property(nonatomic, strong)EOCPanGestureOne *panGesture;
@end
EOCCustomScrollView.m文件
#import "EOCCustomScrollView.h"
@interface EOCCustomScrollView()<UIGestureRecognizerDelegate>
@end
@implementation EOCCustomScrollView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
_panGesture = [[EOCPanGestureOne alloc] initWithTarget:self action:@selector(panAction:)];
_panGesture.delegate = self;
//_panGesture.cancelsTouchesInView = NO;
[self addGestureRecognizer:_panGesture];
return self;
}
//- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer {
//
// NSLog(@"gestureRecognizerShouldBegin %@", gestureRecognizer.view);
// if (self.bounds.origin.x == self.contentSize.width - self.frame.size.width) {
//
// //往左滑動(dòng)
// CGPoint transitionPoint = [gestureRecognizer translationInView:self];
// if (transitionPoint.x < 0) {
// return NO;
// }
// return YES;
// }
//
// return YES;
//}
//或者我試了下實(shí)現(xiàn)下面這個(gè)方法也可以
//- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
//{
// return YES;
//}
- (void)panAction:(UIPanGestureRecognizer *)gestureRecognizer {
NSLog(@"bounds.x %f", self.bounds.origin.x);
CGRect tmpBounds = self.bounds;
//x,y方向移動(dòng)的位置,左上小于0,右下大于0
CGPoint transitionPoint = [gestureRecognizer translationInView:self];
///移動(dòng)距離是有最大值和最小值
CGFloat minimumOffset = 0.f;
CGFloat maxOffset = _contentSize.width - tmpBounds.size.width;
CGFloat actualOffset = fmax(minimumOffset, fmin(maxOffset, (tmpBounds.origin.x - transitionPoint.x)));
tmpBounds.origin.x = actualOffset;
// if (transitionPoint.x < 0 && (tmpBounds.origin.x - transitionPoint.x) <= (_contentSize.width - tmpBounds.size.width)) { //往左
//
// tmpBounds.origin.x -= transitionPoint.x;
//
// } else if (transitionPoint.x > 0 && (tmpBounds.origin.x - transitionPoint.x) >= 0) { //往右
//
// tmpBounds.origin.x -= transitionPoint.x;
//
// }
//設(shè)置移動(dòng)的距離為0,不讓他疊加,不然移動(dòng)的很快
[gestureRecognizer setTranslation:CGPointZero inView:self];
self.bounds = tmpBounds;
}
ViewController.m代碼
EOCCustomScrollView *scrollView = [[EOCCustomScrollView alloc] initWithFrame:CGRectMake(150.f, 88.f, 100, 150.f)];
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width*2, scrollView.frame.size.height);
scrollView.backgroundColor = [UIColor redColor];
[self.view addSubview:scrollView];
///添加子view
UIView *blueView = [[UIView alloc] initWithFrame:CGRectMake(50.f, 20.f, 40.f, 40.f)];
blueView.backgroundColor = [UIColor blueColor];
[scrollView addSubview:blueView];
運(yùn)行之后,滑動(dòng)藍(lán)色View,如圖:
可以發(fā)現(xiàn)。通過添加pan手勢(shì)和改變bounds,實(shí)現(xiàn)了自定義scrollView。
② 模仿系統(tǒng)UIScrollView特性
那么我們?nèi)绾螌?shí)現(xiàn)系統(tǒng)自帶的特性?
打開上面注釋掉的 //_panGesture.cancelsTouchesInView = NO; 以及//gestureRecognizerShouldBegin方法,就可以實(shí)現(xiàn),代碼如上。
ViewController.m代碼如下:
//EOCCustomScrollViewOne以及以下都是繼承EOCCustomScrollView
EOCCustomScrollViewOne *scrollViewOne = [[EOCCustomScrollViewOne alloc] initWithFrame:CGRectMake(0.f, 100.f, self.view.frame.size.width, 300.f)];
scrollViewOne.contentSize = CGSizeMake(self.view.frame.size.width*2, 300.f);
scrollViewOne.backgroundColor = [UIColor redColor];
EOCCustomScrollViewTwo *scrollViewTwo = [[EOCCustomScrollViewTwo alloc] initWithFrame:CGRectMake(0.f, 0.f, self.view.frame.size.width, 300.f)];
scrollViewTwo.contentSize = CGSizeMake(self.view.frame.size.width, 300.f);
scrollViewTwo.backgroundColor = [UIColor blueColor];
// EOCCustomScrollViewThree *scrollViewThree = [[EOCCustomScrollViewThree alloc] initWithFrame:CGRectMake(0.f, 0.f, self.view.frame.size.width, 300.f)];
// scrollViewThree.contentSize = CGSizeMake(self.view.frame.size.width*2, 300.f);
// scrollViewThree.backgroundColor = [UIColor yellowColor];
[self.view addSubview:scrollViewOne];
[scrollViewOne addSubview:scrollViewTwo];
// [scrollViewTwo addSubview:scrollViewThree];
運(yùn)行之后如圖:
藍(lán)色View拖完之后可以把紅色View拖出來,這樣就實(shí)現(xiàn)了系統(tǒng)的那個(gè)特性。