01
Sep
誰能比我更會捲的UIScrollView
Facebook

在iOS 所有的UI 元件裡,擁有捲動神功的UIScrollView 可以說是最強大也最讓人尊敬的開山始祖。無論是讓資料一目瞭然呈現的UITableView,顯示酷炫網頁的UIWebView,千言萬語的文字也能容納的UITextView,全都從UIScrollView 身上學到了捲動的獨門絕技。當然,絕招只有一招是不夠的,聖鬥士星矢也說過,同樣的招式對聖鬥士不能使用兩次。因此UIScrollView 還擁有更多厲害的絕招彼得潘將在下回揭曉,現在就讓我們先來學習他的成名技,捲動神功吧!

撰文:彼得潘

 

加入佔滿整個畫面的UIScrollView 

方法一:從storyboard 中加入UIScrollView 

從Object Library 裡選擇Scroll View,將它輕輕地置放到controller 的view 上,並且調整其大小,使其剛好佔滿整個畫面,尺寸等同於controller view 的大小。 

方法二:從程式生成UIScrollView 

 

ViewController.m 

 

- (void)viewDidLoad 

{

  [super viewDidLoad]; 

  UIScrollView *scrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(0, 0, 320, 

         [UIScreen mainScreen].bounds.size.height-20)]; 

  [self.view addSubview:scrollView]; 

UIScrollView 元件也是UI 元件, 因此將它建立後, 只要利用addSubview: method 即可將它加到controller 的view 上。值得注意的,為了讓scrollView 佔滿整個畫面,我們將它的高度設為 [UIScreen mainScreen].bounds.size.height-20 points,如此才能同時滿足3.5 吋和4 吋兩種尺寸的iPhone。(利用 [UIScreen mainScreen].bounds. size.height 可以取得iPhone 螢幕的高度,扣除20 則是因為status bar 的緣故。) 

 

執行APP 

令人失望的,雖然現在是炎熱的夏天,畫面上卻是冷若冰霜,完全無法捲動的雪白畫面,無論我們上下滑,左右滑都沒反應! 

設定捲動範圍的contentSize 

為什麼不能捲動呢?這是因為我們沒有設定捲動範圍的緣故。就好像參加馬拉松比賽需要知道起點終點才能開始跑,UIScrollView 也要知道捲動的範圍它才知道如何捲動,知道了哪裡是邊界,才知道哪裡是不能越界的捲動終點。(其實還有更高深的無限捲動大法,不過如此高深的大法還是等我們掌握了基本功後再來學習吧。) 

1. 連結storyboard 上UIScrollView 元件

為了控制storyboard 上的UIScrollView 元件,我們必須先建立連結的outlet 變數contentScrollView。 

ViewController.m 

 

@interface ViewController () 

@property (weak, nonatomic) IBOutlet UIScrollView *contentScrollView; 

@end 

 

2. 設定代表捲動範圍的contentSize 

ViewController.m 

 

- (void)viewDidLoad 

{

[super viewDidLoad]; 

! // Do any additional setup after loading the view, typically from a nib. 

self.contentScrollView.contentSize = CGSizeMake(1000, 1000); 

 

UIScrollView 的property contentSize 代表捲動範圍,其宣告如下: 

UIScrollView.h 

 

@property(nonatomic) CGSize contentSize; 

contentSize 的型別為CGSize,因此我們可以設定width 和height, 也就是左右捲動的寬度和上下捲動的高度。注意唯有以下2 種case 可以促成捲動: 

 

左右捲動: contentSize 的寬度 > UIScrollView frame 的寬度

以剛剛的程式碼為例,UIScrollView 的寬度,也就是frame 的width 為320, 而contentSize 則是1000。如此即表示畫面上顯示的scrollView 寬320,但可以左右捲動調整顯示的範圍。換句話說,雖然一次只看得到寬320 的範圍,但我們實際上擁有寬1000 的空間容納其它的UI 元件。以下讓我們以實際的例 子,“捲動三部曲", 來說明捲動的運作原理: 

 

一部曲: 一開始尚未捲動時,顯示寬度1000 空間的左半邊320 points 區塊: 

二部曲:向左捲動158 points 時,顯示x 座標範圍158 到478 的區塊: 

三部曲:向左捲動680 points時,顯示x座標範圍680到1000的區塊: 

因此當scroll view frame 寬320,contentSize 寬1000 時,我們可以向左捲動680 points (1000 - 320),多看到680 points 的空間, 也就是合計水平有1000 points 的空間。因此,我們可以向左捲動的距離將等於contentSize 的寬度減去scroll view 的寬度。若是contentSize 小於或等於scroll view 的寬度,相減後小於等於0,也就代表了沒有向左捲動的距離。因此如果要可以左右捲動,一定要讓相減的結果大於0 才可以。 

 

上下捲動:contentSize 的 高度 > UIScrollView frame 的高度

原理如同左右捲動的例 子。此時可以向上捲動看到超出scroll view frame 範圍的區塊。

 

執行APP 

終於可以捲動了。由於我們將contentSize 設為(1000, 1000), 其寬高皆大於scroll view 的寬高( 320 , 548),所以不管是左右或上下都可以捲動。雖然我們尚未在scroll view 上添加任何UI 元件,但由於捲動時會浮現x 軸和y 軸的scroll bar,所以我們還是可以清楚地感覺到捲動的效果和實際捲動的距離。

 

 

在ScrollView 上加入UI 元件 

為了更清楚地見證捲動的威力,接下來彼得潘將在scroll view 上添加比scroll view 尺寸還大,也就是比畫面還大的圖片來驗證。這時候捲動就有意義了,為了看到圖片的每一寸肌膚,每一個角落,此時不捲,更待何時呢 ! 

ViewController.m 

 

- (void)viewDidLoad 

{

[super viewDidLoad]; 

self.contentScrollView.contentSize = CGSizeMake(1000, 1000); 

U I I mageVi e w * i m a g e V i e w = [ [ U I I mageVi e w a l l o c ] initWithFrame:CGRectMake(10, 10, 845, 635)]; 

imageView.image = [UIImage imageNamed:@"book.jpg"]; 

[self.contentScrollView addSubview:imageView]; 

建立圖片物件imageView,然後將其加到scroll view 上。

 

執行APP 

我們建立的圖片物件寬845,高635,遠遠超過iPhone 5 畫面扣除statusbar 後的(320, 548),因此我們無法一次看到彼得潘著作和希臘英雄的合照。不過好在我們有著contentSize 高達(1000, 1000) 的scroll view, 只要以我們的小指上下左右捲動,即可一覽圖片的全貌。

 

設定捲動位置的contentOset 

當我們捲動scroll view 時,scroll view 乖乖地聽話捲動。背後運作的原理其實是因為scroll view 的contentOset 被改變了。contentOset 的宣告如下: 

UIScrollView.h 

 

@property(nonatomic) CGPoint contentOset; 

 

contentOset 的型別為CGPoint,因此經由控制它的x,我們可以調整scroll view 向左捲動的距離。經由控制它的y,我們可以調整scrollview 向上捲動的距離。例如下圖為scroll view 的contentOset. x 158 points 時的畫面。

當我們繼續向左捲動,一直捲動到邊界時,contentOset.x 將為680,也就是contentSize 的寬度減掉scroll view frame 的寬度即為我們可以向左捲動的最大距離。

除了使用者親手捲動scroll view 改變contentOset 外, 我們也可以從程式控制scroll view 的捲動。以下我們分為瞬間移動版和酷炫動畫版兩種case 來討論。

 

瞬間移動版: 

1. 在畫面上加入觸發scroll view 捲動的按鈕 

ViewController.m 

 

- (void)viewDidLoad 

{

[super viewDidLoad]; 

self.contentScrollView.contentSize = CGSizeMake(1000, 1000); 

U I I mageVi e w * i m a g e V i e w = [ [ U I I mageVi e w a l l o c ] initWithFrame:CGRectMake(10, 10, 845, 635)]; 

imageView.image = [UIImage imageNamed:@"book.jpg"]; 

[self.contentScrollView addSubview:imageView]; 

UIButton *but = [[UIButton alloc] initWithFrame:CGRectMake(30, 30, 100, 30)]; 

[but setTitle:@" 瞬間移動" forState:UIControlStateNormal]; 

[but setTitleColor:[UIColor orangeColor] forState:UIControlStateNormal]; 

[self.view addSubview:but]; 

[but addTarget:self action:@selector(move:) forControlEvents:UIControlE ventTouchUpInside]; 

值得注意的,此時我們將按鈕加到self.view 上,如此按鈕才會固定在畫面上,不會隨著scroll view 一起捲動。

 

2. 定義按鈕按下後觸發捲動的move: method 

ViewController.m 

 

-(void)move:(id)sender 

{

self.contentScrollView.contentOset = CGPointMake(320, 220); 

將contentOset 的x 設為320,y 設為220,即表示scroll view 將往左捲動320 points,往上捲動220 points。

 

執行APP 

點選按鈕後,scroll view 果然如同瞬間移動般捲動了。

 

 

酷炫動畫版: 

1. 設定動畫捲動效果

ViewController.m 

 

-(void)move:(id)sender 

{

[self.contentScrollView setContentOffset:CGPointMake(320, 220) animated:YES]; 

呼叫scroll view 的setContentOset:animated: method 即可產生帶有動畫效果的捲動。此method 的宣告如下: 

UIScrollView.h 

 

- (void)setContentOset:(CGPoint)contentOset animated:(BOOL) animated; 

(1) contentOset: 設定scroll view 的contentOset 

(2) animated: 設定是否要有動畫效果

 

執行APP 

scroll view 的bounce 

前面提到scroll view 的contentOset 控制著捲動的距離。正常情況下,我們可以捲動的最大距離為contentSize 的尺寸減掉scroll view frame 的尺寸。然而,為了帶來更生動的捲動效果,Apple 允許我們捲動時超出contentSize 包含的範圍一點點。Apple 把如此的特異功能稱為bounce,中文翻為彈回。

為什麼稱為bounce 呢? 因為當我們捲動至超出邊界, 然後把手放開時,scroll view 將自動彈回,回到它的邊界。這樣的好處在於可以讓使用者捲動至邊界時不會有突然卡關,突然不能捲動的生硬感覺。當scroll view 可以左右捲動,即表示當我們捲動超過contentSize 的左邊界和右邊界時可產生bounce 效果。同樣的,當scroll view 可以上下捲動,當我們捲動超過contentSize 的上邊界和下邊界時也會產生bounce 效果。以下我們就以上下捲動來說明生動的bounce 效果: 

向下捲動產生的bounce 效果:

假設scroll view 一開始的contentOset.y 為0,也就是其對齊著上邊界。當我們向下捲動,即表示我們已經超過了上邊界,因此當手放開後,將產生自動向上彈回的bounce 效果。

向上捲動產生的bounce 效果: 

假設scroll view 一開始的contentOset.y 為contentSize 的高度減掉scroll view frame 的高度,也就是此時其對齊著下邊界。當我們繼續向上捲動,即表示我們已經超過了下邊界,因此當手放開後,將產生自動向下彈回的bounce 效果。

 

關於彼得潘

如果我會作詞作曲,我就能成為創作歌手。

我有一絲音感嗎?沒有。

所以,很可惜,我只能當歌手的朋友。

如果給我一天一個App 的負荷,

也澆不熄我對蘋果的熱情。

一天能夠完成一個App 嗎?可以。

所以,是的,我是愛瘋一切為蘋果的彼得潘。

著有App 程式設計入門 (博客來電腦類Top 1)

facebook:http://www.facebook.com/iphone.peterpan