UIScrollView為了顯示多於一個屏幕的內容或者超過你能放在內存中的內容。Scroll View為你處理縮小放大手勢,UIScrollView實現了這些手勢,並且替你處理對於它們的探測和回應。其中需要注意的子類是UITableView以及UITextView(用來顯示大量的文字)。還有一個UIWebView,盡管那不是UIScrollView的直接子類,它適用UIScrollView去顯示網頁內容contentsize是內容的寬和高,contentsize.width是內容的寬度,contentsize.heght是高度,contentsize是UIScrollView的一個屬性,它是一個CGSize,是由核心圖形所定義的架構,那定義了你可以滾軸內容的寬度和高度,你也可以添加可以上下滾動的額外區域。第一種方法是你可以通過添加內容的大小來完成。另外一個比較動態的選擇是UIScrollView的另一個屬性contentInset,contentInset增加你在contentsize中指定的內容能夠滾動的上下左右區域數量contentInset.top以及contentInset.buttom分別表示上面和下面的距離。
在滾軸視圖中,有一個叫做ContentOffset的屬性跟蹤UIScrollView的具體位置,你能夠自己獲取和設置它,ContentOffset是你當前可視內容在滾軸視圖邊界的左上角那個點。如圖:
可以看出,ContentOffset內容中的那個點不是從contentInset的左上角開始的,而是內容的左上角,此時的ContentOffset是正值,但有時也是負值,如下圖所示:
使用一個ScrollView
創建一個UIScrollView
復制代碼 代碼如下:
CGRectframe = CGRectMake( 0, 0, 200, 200);
scrollView= [[UIScrollView alloc] initWithFrame: frame];
添加子視圖(框架可以超過scroll view的邊界)
復制代碼 代碼如下:
frame= CGRectMake( 0, 0, 500, 500);
myImageView= [[UIImageView alloc] initWithFrame: frame];
[scrollViewaddSubview:myImageView];
設置內容尺寸
復制代碼 代碼如下:
scrollView.contentSize= CGSize(500,500);
擴展Scroll View 的行為
通過子類化擴展Scroll View 的行為
應用程序的邏輯和行為變成了視圖本身的一部分,就像,你可能有一些定制的滾軸邏輯,,在那你只在意一個視圖控制,但你想在不同地方重復使用你的滾軸視圖,如果你必須為每個都子類化,你最後會有很多不同的滾軸視圖子類以及在視圖中的特定應用邏輯。
編寫很多子類是很沉悶的事情,你最後會有很多無法重復使用的單獨視圖,而MVC的視圖部分的一個重點是視圖是可以在不同的控制器和不同的模式之中重復使用的,如果我們把所有邏輯都放在視圖中,它減少了可復用性。
你的代碼變得很牢固地配對在一起,它實際上變成了超類的一部分,你無法從UIScrollView中析取它,之後用其它東西代替,如果它在你控制器中且為控制器的一部分,在之後更容易改變它工作的方式和重新安排你應用程序的一些部分。
通過委派來擴展Scroll View 的行為(常用的)
委派是一個單獨的對象,協議,定義了委派會實現的一系列功能的Objective-C協議,它創建了一系列很清晰的撤銷點,在那裡你能定制行為和外觀。它在這些對象之間保持了松散的配對,視圖本身與視圖控制器或任何其它的控制器對象,委派不是滾軸視圖的直接子類,它比起牢固配對的子類更加的松散。
理論篇
在滾動的過程中,實際上就是contentOffset的值在不斷變化,當手指觸摸後,UIScrollView會暫時攔截觸摸事件,使用一個計時器。假如在計時器到點後沒有發生手指移動事件,那麼UIScrollView發送 tracking events 到被點擊的 subview 上面。如果在計時器到點前發生了移動事件,那麼UIScrollView取消 tracking 然後自己發生滾動。
可以重載子類
復制代碼 代碼如下:
- (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view;
決定自己是否接受 touch 事件
復制代碼 代碼如下:
- (BOOL)touchesShouldCancelInContentView:(UIView *)view;
開始發送 tracking messages 給 subview 的時候調用這個方法,決定是否發送 tracking messages 消息到 subview。
返回NO -> 發送,表示不取消
返回YES -> 不發送,表示會取消
屬性篇
復制代碼 代碼如下:
@property(nonatomic,readonly,getter=isTracking) BOOL tracking;
當 touch 後還沒有拖動的時候值是YES,否則NO
復制代碼 代碼如下:
@property(nonatomic,readonly,getter=isZoomBouncing) BOOL zoomBouncing;
當內容放大到最大或者最小的時候值是YES,否則NO
復制代碼 代碼如下:
@property(nonatomic,readonly,getter=isZooming) BOOL zooming;
當正在縮放的時候值是YES,否則NO
復制代碼 代碼如下:
@property(nonatomic,readonly,getter=isDecelerating) BOOL decelerating;
當滾動後,手指放開但是還在繼續滾動中。這個時候是YES,其他時候是NO
復制代碼 代碼如下:
@property(nonatomic) CGFloat decelerationRate;
設置手指放開後的減速率
復制代碼 代碼如下:
@property(nonatomic) CGFloat maximumZoomScale;
表示放大的最大倍數
復制代碼 代碼如下:
@property(nonatomic) CGFloat minimumZoomScale;
表示縮小的最小倍數
復制代碼 代碼如下:
@property(nonatomic,getter=isPagingEnabled) BOOL pagingEnabled;
當值為YES的時候,就會產生翻頁那種效果
復制代碼 代碼如下:
@property(nonatomic,getter=isScrollEnabled) BOOL scrollEnabled;
決定是否可以滾動
復制代碼 代碼如下:
@property(nonatomic) BOOL delaysContentTouches;
當值為YES的時候,用戶一旦觸碰,然後再一定時間內沒有移動,UIScrollView會發送 tracking events,然後用戶移動手指足夠長度觸發滾動事件,這個時候,UIScrollView發送了-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event到 subview,然後UIScrollView開始滾動。假如值為NO,UIScrollView發送 tracking events 後,就算用戶移動手指,UIScrollView也不會滾動。
復制代碼 代碼如下:
@property(nonatomic) BOOL showsHorizontalScrollIndicator;
滾動時是否顯示水平滾動條
復制代碼 代碼如下:
@property(nonatomic) BOOL showsVerticalScrollIndicator;
滾動時是否顯示垂直滾動條
復制代碼 代碼如下:
@property(nonatomic) BOOL bounces;
默認是YES,就是滾動超過邊界會有反彈回來的效果,如果設置為NO,那麼滾動到邊界就會立刻停止
復制代碼 代碼如下:
@property(nonatomic) BOOL bouncesZoom;
這個效果反映在縮放上面,如果縮放超過最大縮放,就會有反彈效果,加入設置為NO,則達到最大或者最小的時候立刻停止
復制代碼 代碼如下:
@property(nonatomic,getter=isDirectionalLockEnabled) BOOL directionalLockEnabled;
默認是NO,可以在垂直和水平方向同時運動。當值為YES的時候,加入一開始是垂直或者水平運動,那麼接下來會鎖定另外一個方向的滾動。加入一開始是對角方向滾動,則不會禁止某個方向
復制代碼 代碼如下:
@property(nonatomic) UIScrollViewIndicatorStyle indicatorStyle;
滾動條的樣式,基本只是設置顏色
復制代碼 代碼如下:
@property(nonatomic) UIEdgeInsets scrollIndicatorInsets;
設置滾動條的位置
方法篇
直接上代碼看
復制代碼 代碼如下:
#pragma mark UIScrollViewDelegate
//只要滾動了就會觸發
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
{
// NSLog(@" scrollViewDidScroll");
NSLog(@"ContentOffset x is %f,yis %f",scrollView.contentOffset.x,scrollView.contentOffset.y);
}
//開始拖拽視圖
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
{
NSLog(@"scrollViewWillBeginDragging");
}
//完成拖拽
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
{
NSLog(@"scrollViewDidEndDragging");
}
//將開始降速時
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView;
{
NSLog(@"scrollViewWillBeginDecelerating");
}
//減速停止了時執行,手觸摸時執行
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
{
NSLog(@"scrollViewDidEndDecelerating");
}
//滾動動畫停止時執行,代碼改變時出發,也就是setContentOffset改變時
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView;
{
NSLog(@"scrollViewDidEndScrollingAnimation");
}
//設置放大縮小的視圖,要是uiscrollview的subview
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView;
{
NSLog(@"viewForZoomingInScrollView");
return viewA;
}
//完成放大縮小時調用
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale;
{
viewA.frame=CGRectMake(50,0,100,400);
NSLog(@"scale between minimum and maximum. called after any 'bounce' animations");
}// scale between minimum and maximum. called after any 'bounce' animations
//如果你不是完全滾動到滾軸視圖的頂部,你可以輕點狀態欄,那個可視的滾軸視圖會一直滾動到頂部,那是默認行為,你可以通過該方法返回NO來關閉它
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView;
{
NSLog(@"scrollViewShouldScrollToTop");
returnYES;
}
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView;
{
NSLog(@"scrollViewDidScrollToTop");
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
@end
基於AotoLayout的控件懸停
在很多的電商類的APP中我們經常會用到一種滑動懸停的效果,所以這次嘗試著利用AutoLayout來實現這種效果。話不多說先上圖
效果:
效果上過了,還是理順下思路上吧。從效果圖上不難發現我們的程序是基於UIScrollerView的。界面上的展示同樣都是UIScrollView的子控件。當然了難點也就是基於UIScrollerView的內部子控件的布局以及AutoLayout約束的修改。
我們看一下我們的需要的一些基本的控件布局:
好了,現在開始開始搭建新的項目了,首先在Storyboard上搭建最基本的布局,並將控件基本布局到相應的位置,以備我們更方便的添加約束。
到了這裡就開始對我們的控件添加基本的約束了從上開始吧。上面的控件我們能確定是UIImageView的heigth以及top、left以及rihgt。至於bottom到了後面會著重設置的。
然後繼續來操作懸停的View。這個View因為處在兩個view的中間所以我們要暫時確定height以及left和right的約束就足夠了了
終於到最後一個UIImageView了,同樣的能確定的是height、right、left以及bottom這四項。
到了這裡針對單個的控件的布局基本完成了,細心的你可能會發現以上的操作都沒有進行關聯以及沒有對width進行約束。那麼下面我們就要開始進行三者的關聯以及width上的設置。
因為懸停的view處與兩個imageView的中間,所以我們只需要對它進行處理就好了
其實這兩個約束完全是可以在設置懸停view的時候的添加,之所以在這裡添加是我們在後面的控制器會用到這兩個約束,為了好辨認我們就給他們定義上別名:
然後我們按住command選擇我們的三個控件然後按住control拖向scrollView的父控件view設置等width。
好了,到了這裡我們的約束已經不會再報錯了,然後在邏輯上也是基本是通順的了,但是對於我們懸停的功能來說就少了關鍵的一個環節那麼就是一旦懸停view懸停了,那麼它與上下兩個imageView的約束要怎麼處理呢?所以我們還需要一個輔助的約束用來懸停之後的關聯處理
添加之後我們給這個約束起個別名hidden懸停,不過細心的你一定會發現添加之後會sb會提示出有沖突的約束,那麼我們只需要對我們剛剛添加的約束進行一下操作就OK了
好了,到了這裡我們在AutoLayout上要做的事情就這些了,下面我們就可以安心的寫代碼了。
復制代碼 代碼如下:
@interface ViewController ()/**中間的View*/@property (weak, nonatomic) IBOutlet UIView *centreView;/**上面的ImageView*/@property (weak, nonatomic) IBOutlet UIImageView *topImageView;/**centreView的top約束*/@property (weak, nonatomic) IBOutlet NSLayoutConstraint *topConstraint;/**centreView的bottom約束*/@property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomConstraint;/**topImageView暫禁用的約束*/@property (weak, nonatomic) IBOutlet NSLayoutConstraint *hiddenConstraint;
@end
然後在scrollView的滑動代理方法中實現
復制代碼 代碼如下:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
//獲得top圖片的高度
CGFloat imageH = self.topImageView.frame.size.height;
//獲取偏移量
CGFloat offsetY = scrollView.contentOffset.y;
//centrView的frame
CGRect centreFrame = self.centreView.frame;
if (offsetY>=imageH) {
//將centreView的向上和向下的約束禁用
self.bottomConstraint.active = NO;
self.topConstraint.active = NO;
//將topImageView與bottomImageView的約束使用關聯
self.hiddenConstraint.active = YES;
//懸停在位置
centreFrame.origin.y = 0;
self.centreView.frame = centreFrame;
//添加在scrollView的父控件
[self.view addSubview:self.centreView];
}else{
//添加在topIamgeView的下面
centreFrame.origin.y = imageH;
self.centreView.frame = centreFrame;
[scrollView addSubview:self.centreView];
//記住一定要先添加到scrollView上之後在修改約束的內容,不然添加的約束會不成,因為系統無法建立他們之間的聯系。
self.hiddenConstraint.active = NO;
self.topConstraint.active = YES;
self.bottomConstraint.active = YES;
}
//等比例的伸縮
CGFloat scale= 1-(offsetY/60);
scale = (scale>=1)?scale :1;
self.topImageView.transform = CGAffineTransformMakeScale(scale, scale);
}
最後補充:
在子控件的尺寸不能通過UIScrollView來計算,可以通過以下方式計算
在UIScrollView裡面布局子控件,sb裡面默認是是需要子控件的尺寸以及子控件與UIScrollView之間的間距來計算出scroller的conentSize的。所以這就是上面的子控件要與父控件來等寬的原因了。
UIScrollView的frame應該通過子控件以外的其他控件來計算。