一. UIScrollView屬性、方法和代理
1. UIScrollView屬性、方法
自定義系統(tǒng)的UIScrollView之前我們要先解釋UIScrollView的屬性、方法,代理方法,因?yàn)閁IScrollView的有些屬性還是比較難理解的,直接拷貝系統(tǒng)UIScrollView.h文件一個一個解釋,如下:
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)于這三個屬性,網(wǎng)上有太多的解釋,可參考:http://www.itdecent.cn/p/e5582fe5dd4a
下面補(bǔ)充一下,iOS7之后出現(xiàn)了下面這個屬性,這個屬性是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
這個屬性的字面意思是自動調(diào)節(jié)ScrollView的Insets,默認(rèn)YES,比如我們像如下創(chuàng)建scrollView的時候:
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,我們就要手動設(shè)置ScrollVIew的Insets,代碼如下:
//其中88是導(dǎo)航欄的高度
scrollView.contentInset = UIEdgeInsetsMake(88.f, 0.f, 0.f, 0.f);
scrollView.contentOffset = CGPointMake(0.f, -88.f);//這句如果不寫,不會自動滾動到指定位置
- 如果我們把a(bǔ)utomaticallyAdjustsScrollViewInsets設(shè)置為NO,又沒手動設(shè)置ScrollVIew的Insets,這時候就需要調(diào)節(jié)scrollView的frame的y值了。
這個屬性在iOS11已廢棄,使用UIScrollView的如下屬性代替,和上面屬性一樣的,默認(rèn)自動調(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));
下面再介紹一個屬性
safeAreaInsets
@property (nonatomic,readonly) UIEdgeInsets safeAreaInsets API_AVAILABLE(ios(11.0),tvos(11.0));
iOS11之后,由于劉海屏幕的出現(xiàn)有了這個屬性,它是安全區(qū)域之外的額外inserts,比如iPhone X豎屏?xí)r占滿整個屏幕的控制器的view的safeAreaInsets是(44,0,34,0) ,44和34分別是劉海和手勢操作的區(qū)域。
說了這么多介紹下面這個屬性就很簡單了
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時:
adjustedContentInset = safecontentInset + scrollView.contentInset - 當(dāng)設(shè)置UIScrollViewContentInsetAdjustmentNever時:
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屬性改變會調(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的時候,會鎖住水平或豎直方向的滑動
@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 豎直方向總是可以彈性滑動,默認(rèn)是NO, 當(dāng)設(shè)置為TRUE(前提是屬性bounces必須為TRUE)的時候,即使contentSize設(shè)置的width 和 height都比UIScrollView的width 和 height小,在垂直方向上都可以有滑動效果,甚至即使我們不設(shè)置contentSize都可以產(chǎn)生滑動效果; 反之,如果設(shè)置alwaysBounceVertical為FALSE, 那么當(dāng)contentSize設(shè)置的width 和 height都比UIScrollView的width 和 height小的時候,即使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
//是否可分頁,默認(rèn)是FALSE, 如果設(shè)置成TRUE, 則可分頁
@property(nonatomic,getter=isPagingEnabled) BOOL pagingEnabled __TVOS_PROHIBITED;// default NO. if YES, stop on multiples of view bounds
//是否可以滾動
@property(nonatomic,getter=isScrollEnabled) BOOL scrollEnabled; // default YES. turn off any dragging temporarily
//是否展示水平方向滾動條
@property(nonatomic) BOOL showsHorizontalScrollIndicator; // default YES. show indicator while we are tracking. fades out after tracking
//是否展示垂直方向滾動條
@property(nonatomic) BOOL showsVerticalScrollIndicator; // default YES. show indicator while we are tracking. fades out after tracking
//滑動條的邊緣插入,即是距離上、左、下、右的距離
//例如:testScrollView.scrollIndicatorInsets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
//當(dāng)向下滑動時,滑動條距離頂部的距離總是20
@property(nonatomic) UIEdgeInsets scrollIndicatorInsets; // default is UIEdgeInsetsZero. adjust indicators inside of insets
//滾動條樣式,是個枚舉類型:
@property(nonatomic) UIScrollViewIndicatorStyle indicatorStyle; // default is UIScrollViewIndicatorStyleDefault
//減速率,CGFloat類型,當(dāng)你滑動松開手指后的減速速率, 但是盡管decelerationRate是一個CGFloat類型,但是目前系統(tǒng)只支持以下兩種速率設(shè)置選擇:
UIScrollViewDecelerationRateNormal 值是 0.998
UIScrollViewDecelerationRateFast 值是 0.99
@property(nonatomic) UIScrollViewDecelerationRate decelerationRate NS_AVAILABLE_IOS(3_0);
//索引展示模式,是個枚舉 : 自動顯示或隱藏 || 一直隱藏
@property(nonatomic) UIScrollViewIndexDisplayMode indexDisplayMode API_AVAILABLE(tvos(10.2));
方法
//帶有動畫效果的設(shè)置偏移量
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; // animate at constant velocity to new offset
//滑動到指定的可見區(qū)域(帶動畫),意思就是滑動到CGRect所組成的矩形區(qū)域,使其可見. 如已可見則什么都不做
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated; // scroll so rect is just visible (nearest edges). nothing if rect completely visible
// 設(shè)置的時候,當(dāng)頁面加載成功出現(xiàn)時,滑動條會自動顯示出來,停留一下又自動隱藏
// 不設(shè)置的話,頁面出現(xiàn)時也不會顯示滑動條,只有在滑動過程中會顯示滑動條
- (void)flashScrollIndicators; // displays the scroll indicators for a short time. This should be done whenever you bring the scroll view to front.
屬性
//是否一直追蹤
//返回YES表示用戶手指一直接觸著scrollView(包括手指一直拖動scrollView)沒有松開 返回NO表示手指離開scrollView 此時scrollView做自由滾動
@property(nonatomic,readonly,getter=isTracking) BOOL tracking; // returns YES if user has touched. may not yet have started dragging
//是否正在拖動
@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)于這兩個屬性暫時只說一點(diǎn),當(dāng)scrollView上面添加一個UISliderView的時候,如果拖動UISliderView,scrollView會移動,當(dāng)我們設(shè)置delaysContentTouches = NO,canCancelContentTouches = NO的時候,UISliderView和scrollView的拖動才互不影響。
更多詳情參考:http://www.itdecent.cn/p/2c74b7a6c082
touchesShouldBegin和touchesShouldCancelInContentView
//必須子類化之后重寫,用以控制將觸摸事件傳遞給scrollView的子視圖
//在觸摸被傳遞到scrollView的子視圖之前調(diào)用。如果它返回NO,觸摸將不會被傳遞到子視圖
//這對press沒有影響
//默認(rèn)返回YES
- (BOOL)touchesShouldBegin:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event inContentView:(UIView *)view;
//如果觸摸已經(jīng)傳遞到滾動視圖的子視圖,則在滾動開始之前調(diào)用。如果返回NO,觸摸將繼續(xù)傳遞到子視圖,滾動將不會發(fā)生
//如果canCancelContentTouches為NO,則不調(diào)用。如果視圖不是UIControl,默認(rèn)返回YES
- (BOOL)touchesShouldCancelInContentView:(UIView *)view;
關(guān)于動畫的一些屬性:
minimumZoomScale和maximumZoomScale
//滑動視圖的最小縮放倍數(shù),默認(rèn)是1.0
@property(nonatomic) CGFloat minimumZoomScale; // default is 1.0
//滑動視圖的最大縮放倍數(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?方法,否則無法實(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)
}
其他動畫屬性
//設(shè)置縮放比例,帶動畫
- (void)setZoomScale:(CGFloat)scale animated:(BOOL)animated NS_AVAILABLE_IOS(3_0);
//把從scrollView里截取的矩形區(qū)域縮放到整個scrollView當(dāng)前可視的frame里面。如果截取的區(qū)域大于scrollView的frame時,圖片縮小,如果截取區(qū)域小于frame,會看到圖片放大。一般情況下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)縮放到最大或最小值的時候不會有彈性效果
@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的時候,如果正在縮放過程中則為false,如果縮放到最小值或者最大值時松開手指則為true; 當(dāng)設(shè)置bouncesZoom = false的時候,如果正在縮放過程中zoomScale > 1時則為false,并且縮放到最大值時松開手指也是false。
@property(nonatomic,readonly,getter=isZoomBouncing) BOOL zoomBouncing; // returns YES if we are in the middle of zooming back to the min/max value
//滑動到頂部,默認(rèn)是true,當(dāng)點(diǎn)擊狀態(tài)欄的時候,如果當(dāng)前UIScrollView不是處在頂部位置,那么可以直接回到頂部;如果已經(jīng)在頂部,則沒有作用;另外必須注意如果要這個屬性起作用,它的delegate方法func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool不能返回false,否則沒用。
@property(nonatomic) BOOL scrollsToTop __TVOS_PROHIBITED; // default is YES.
//平移手勢,get屬性,這個手勢的代理系統(tǒng)設(shè)置的scrollView,不能修改為其他的. 可以通過設(shè)置平移手勢的屬性來改變平移方式,比如設(shè)置觸摸手指的最少個數(shù)minimumNumberOfTouches
@property(nonatomic, readonly) UIPanGestureRecognizer *panGestureRecognizer NS_AVAILABLE_IOS(5_0);
//捏合手勢,也就是縮放手勢,get屬性,設(shè)置同平移手勢一樣,當(dāng)縮放禁用時返回nil。
@property(nullable, nonatomic, readonly) UIPinchGestureRecognizer *pinchGestureRecognizer NS_AVAILABLE_IOS(5_0);
// 定向按壓手勢,給tvos用的
@property(nonatomic, readonly) UIGestureRecognizer *directionalPressGestureRecognizer ;
//鍵盤消失模式, 默認(rèn)是none,是個枚舉值
public enum UIScrollViewKeyboardDismissMode : Int {
case none // 無
case onDrag // 拖拽,只要滑動UIScrollView,鍵盤消失
case interactive // 交互式,拖住UIScrollView一直下滑,當(dāng)接觸到鍵盤時,鍵盤就跟著同步下滑
}
@property(nonatomic) UIScrollViewKeyboardDismissMode keyboardDismissMode NS_AVAILABLE_IOS(7_0); // default is UIScrollViewKeyboardDismissModeNone
//系統(tǒng)的刷新控件,就是一個菊花
@property (nonatomic, strong, nullable) UIRefreshControl *refreshControl NS_AVAILABLE_IOS(10_0) __TVOS_PROHIBITED;
更多關(guān)于圖片縮放,請參考:iOS圖片縮放
2. UIScrollView代理方法
//已經(jī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
//即將開始拖拽(或許要拖拽移動一小段距離才會調(diào)用),只要一開始拖拽就會調(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停止時就調(diào)用
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; // called when scroll view grinds to a halt
//已經(jīng)結(jié)束滑動動畫,這個方法起作用的前提是設(shè)置了下滿面提到的兩種方法中的任意一種,否則不會起作用!
// 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
//返回一個需要縮放的視圖,需要做縮放的時候必須調(diào)用此方法,之前已經(jīng)講過,不再贅述!
- (nullable UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView; // return a view that will be scaled. if delegate returns nil, nothing happens
//即將開始縮放,在滑動視圖開始縮放它的內(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é)束縮放手勢時調(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
//滑到頂部,這個方法和屬性scrollsToTop用法一致,本質(zhì)一致,返回true則是可以滑動頂部,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)滑動到頂部的動畫完成的時候調(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)然這個代理方法是給外界用的
- (void)scrollViewDidChangeAdjustedContentInset:(UIScrollView *)scrollView API_AVAILABLE(ios(11.0), tvos(11.0));
二. UIScrollView原理和自定義UIScrollView
1. UIScrollView原理
- 通過上面的屬性介紹,我們發(fā)現(xiàn)系統(tǒng)的UIScrollView有一個pan手勢,pan手勢的代理是UIScrollView,并且不能修改pan手勢的代理。
- 到底UIScrollView是怎么滑動的呢?
UIScrollView繼承于UIView,然后自己上面添加一個手勢,UIScrollView實(shí)質(zhì)就是根據(jù)手勢的滑動修改它的bounds來進(jìn)行view的滑動,可以在代理方法里打印ScrollView的bounds值來驗(yàn)證。
2. 自定義UIScrollView
系統(tǒng)的UIScrollView特點(diǎn):
首先看一下系統(tǒng)的UIScrollView的一個特點(diǎn),如下代碼:
//灰色的scrollView添加到紅色的scrollView上
//系統(tǒng)UIScrollView現(xiàn)實(shí)情況是,可以滑動到父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上面添加一個灰色的scrollView,灰色的scrollView滑完之后可以滑動到紅色的scrollView上。
效果圖如下:
按照我們iOS手勢手勢被識別之后是不會再傳給父控件了,但是這是怎么回事呢?
我們先屏蔽系統(tǒng)的這個特點(diǎn),新建一個EOCScrollView類繼承于UIScrollView,重寫以下方法:
EOCScrollView.m代碼
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
self.panGestureRecognizer.delegate = self;
}
return self;
}
#pragma mark - 重寫手勢代理,如果是右滑,則禁用掉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滑完之后也不會滑到scrollViewTwo上面了。
① 自定義ScrollView
下面我們就自定義一個scrollView,默認(rèn)是沒有如上特性的,我們實(shí)現(xiàn)如上特性
分兩步:
自定義一個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) {
//
// //往左滑動
// CGPoint transitionPoint = [gestureRecognizer translationInView:self];
// if (transitionPoint.x < 0) {
// return NO;
// }
// return YES;
// }
//
// return YES;
//}
//或者我試了下實(shí)現(xiàn)下面這個方法也可以
//- (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方向移動的位置,左上小于0,右下大于0
CGPoint transitionPoint = [gestureRecognizer translationInView:self];
///移動距離是有最大值和最小值
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è)置移動的距離為0,不讓他疊加,不然移動的很快
[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)行之后,滑動藍(lán)色View,如圖:
可以發(fā)現(xiàn)。通過添加pan手勢和改變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)的那個特性。