作為一個iOS 開發者,很多情況下會需要把一個和螢幕等寬的 contentview 新增到一個 scollrview 內部中。大多數的 app 需要有響應式佈局,所以使用 Autolayout 可謂明智之選。第一次學習 scrollviews 時,你或許會覺得碉堡了。但它們奇怪的規則也會令人有點沮喪。
準備
你對 Xcode 和 interface builder 都足夠熟悉。
在 ScrollView 內的 Content View
這篇文章重點在 autolayout,所以我要用各種方式來告訴你如何處理它。第一個方式是 interface builder,因為這是最直觀的。
設定垂直滾動檢視的 interface builder
建立一個新的單檢視控制器應用程式,開啟 storyboard,新增一個 scrollview 到 storyboard 上的ViewController 上。使 scrollview 填滿整個 viewcontroller 的 view。然後使用 storyborad 右下角的 pin 選單,給 scrollview 新增上、左、右、下的約束。確保你沒有選中“constrain to margin”核取方塊。圖1 顯示了正確設定的 pin 選單。這將新增 scrollview 和它的父檢視之間的約束。
圖1:給scrollview新增 上,左,右下的約束。
現在你想新增一個標準的 view 到 scrollview 上。就如你剛剛給 scrollview 新增上、左、右、下的約束那樣,給這個新 view 也新增這些約束,並確保這些約束的值都是0。
當你完成後,通過選中 view 皮膚中的 view ,檢視在右側的 Size Inspector, 可以檢視新增到 scrollview 和 Container view 的約束。圖2 顯示了內容檢視, 但 scroll view 應該看起來也是一樣的。
圖2:Size inspector 顯示在 scrollview 和 container view 上的約束。
ok,現在容器檢視裡已經有了一個 container view。有人會認為,因為我們給 scrollview 的邊緣新增了上、左、下、右的約束,所以這個 container view 將會一直在 scrollview 的上面,並且擁有和視窗一樣的寬度。但事實並非如此,因為 scrollviews 略有不同,這些約束定義了 scrollview 的 content size ,但因為我們的 view 沒有一個確切的 width 或 height ,所以這個 content size 的 wide 和 tall 都是0。
通常我們都想要一個 scrollview 只能垂直滾動,所以並不想讓 container view 比視窗寬,而高度我們通常是想要動態的,所以它會像其內部的 contents 一樣高,稍後再說動態高度,現在將解決固定寬度的問題。
設定垂直滾動
我們要確保 containner view 的寬度不會超過 window 的大小,為此我們將 container view 和 main view (包含 ScrollView 的檢視)設定成等寬,在 view inspector 中,按著 ctrl 拖拽 main view 到container view 並從彈出的選單中選中 EqualWidths。圖3 顯示了被連線的2個檢視。
圖3 按著 Ctrl 拖拽 main view 向 container view
設定動態 Container view 高
最後一步,沒有那麼多的步驟指導。基本上,為了這項工作,container view 上所有的子檢視必須要有一個高度。一些子檢視可以使用它們固有的高度,通常是由它們的寬來決定的。一般地,你需要為任何沒有包含文字的檢視指定寬度和高度。這些包含文字的檢視至少有一個指定的寬或 margins。
第一個和最後一個檢視應該分別以 container view 的頂部和底部分別固定。例如 在圖4中,注意在圖4中所有的檢視都有左和右的約束,這將決定每個檢視的寬度。labels 不具有高度的約束,因為它將取決於其內容的大小。方形的檢視有確切的高度約束。還要注意所有的檢視頂部和底部之間有一個顯示的垂直約束。綜合這些就能決定 contanier view 的高,還有scrollview 的 content size。
在圖4中顯示如下約束。
- Top Label
- 距 container view 頂約束:50pt
- left: 15pt
- right: 15pt
- 底部約束(同為box頂約束): Standard
- Box label
- 頂部約束(同為top label底約束): Standard
- left:15pt
- right: 15pt
- 底部約束(同為bottom label頂約束): Standard
- 高度:86pt;
- 底部label
- 居上約束(同為box底約束): Standard
- left:15pt
- right:15pt
- 距 container view 底約束: Standard
圖4 新增子檢視到容器檢視示例
注意:如果要想 label 的 content 自動增長,就要先選中label並在屬性檢查器重設定 label 的行數為”0”。這樣 label 的行數就沒有限制了。
現在給底部的lable設定一段文字並執行這個例子,將會觸發垂直滾動的條件。圖5 顯示了堆疊檢視。從圖5中可以看出,在容器檢視頂部圖4所示的單個檢視,容器檢視堆放在 scrollview 上,然後是 main view,最後是 window。
圖5 模擬堆疊檢視
設定垂直滾動檢視程式設計
ok,這個部分將希望鞏固最後一節概念,建立一個single-view appliction的專案並開啟ViewController.h檔案。
注意:如果你熟練使用 Xcode 你也可以在原來的 storybord 上拖進一個新的 ViewController,建立一個類,設定這個ViewController的類為你新建立的那個類。你可以在右側的Identity Inspector裡設定.然後把這些類目放入一標題欄裡。
在ViewController.h裡,需要建立以下屬性:
- “contentView”命名的UIView
- “scrollView”命名的UIScrollView
- 2個UILabel ”topLabel”和”bottomLabel”
- “boxView”命名的UIView
當你完成你的程式碼後,你的ViewController.h檔案,應該和程式碼1一樣。
程式碼1 設定屬性
1 2 3 4 5 6 7 8 9 10 11 |
@interface ViewControllerTwo : UIViewController @property (strong, nonatomic) UIView *contentView; @property (strong, nonatomic) UIScrollView *scrollView; @property (strong, nonatomic) UILabel *topLabel; @property (strong, nonatomic) UILabel *bottomLabel; @property (strong, nonatomic) UIView *boxView; @end |
在 ViewController.m 裡,第一步需要在 viewDidload 中需要設定scrollView的寬度。如程式碼2中所示的新增 scrollView 和 contentView。
程式碼2:在主檢視中新增滾動檢視和內容檢視
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
- (void)viewDidLoad{ [super viewDidLoad]; self.scrollView = [[UIScrollView alloc] init]; self.scrollView.translatesAutoresizingMaskIntoConstraints = NO; self.scrollView.backgroundColor = [UIColor blueColor]; [self.view addSubview:self.scrollView]; self.contentView = [[UIView alloc] init]; self.contentView.backgroundColor = [UIColor redColor]; self.contentView.translatesAutoresizingMaskIntoConstraints = NO; [self.scrollView addSubview:self.contentView]; //.... |
再一次我們設定 scrollview 相對於 view 的 margins 為0 ,contentView 相對於 content viewmarigins 為0。最後將 content view 的 width 與 main view 的 width 設為一致。這些都是以程式設計方式新增的約束。將程式碼3中的程式碼新增到 viewDidLoad 方法裡程式碼2的後面。
程式碼3:新增contentView和scrolView的約束
1 2 3 4 5 6 7 8 9 10 |
NSDictionary *tmpViewsDictionary = @{@"scrollView":self.scrollView, @"contentView":self.contentView}; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[scrollView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(0)-[scrollView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[contentView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(0)-[contentView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1 constant:0]]; |
在這份程式碼中,我們使用了兩種不同型別的約束。第一種使用的是 Visual format language,這是一種很好的新增多個約束的字串描述。例如:
- |意思是superview
- .意思是view之間的空間(單個 – 意味著 standard)例如.-(0)- vs. –
- (某個值value)意思是寬
- [某個view]意思是一個view
把它們連線在一起作為第一個約束 @“V:|-(0)-[scrollView]-(0)-|”
這行程式碼的意思是給scrollview新增距 superview 左右都為0的 margin。
更多關於visual format languge 請檢視文件:Visual Format Language
寬度約束是將約束新增到檢視的標準方式,你可以看得更清楚,但是有點繁瑣。
最後,呼叫一個新的方法,把剩餘的檢視新增到content view中,我們把它叫做 addContentSubViews。
程式碼4 展示了這個複雜的 viewDidLoad 方法。
程式碼4 完整的 viewDidLoad 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
- (void)viewDidLoad{ [super viewDidLoad]; self.scrollView = [[UIScrollView alloc] init]; self.scrollView.translatesAutoresizingMaskIntoConstraints = NO; self.scrollView.backgroundColor = [UIColor blueColor]; [self.view addSubview:self.scrollView]; self.contentView = [[UIView alloc] init]; self.contentView.backgroundColor = [UIColor redColor]; self.contentView.translatesAutoresizingMaskIntoConstraints = NO; [self.scrollView addSubview:self.contentView]; //Auto Layout Constraints for scrolling content view NSDictionary *tmpViewsDictionary = @{@"scrollView":self.scrollView, @"contentView":self.contentView}; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[scrollView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(0)-[scrollView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[contentView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(0)-[contentView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1 constant:0]]; [self addContentSubViews]; } |
addContentSubViews方法十分簡單,僅僅建立了幾個label和一個box view.label的numberoflines = 0,意味著lable的content會滿足多行的需求。label 居中且自動換行。
程式碼5 展示了應該新增下面的viewDidLoad方法完整的方法。
程式碼5: addContentSubViews 實現
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
- (void)addContentSubViews{ self.topLabel = [[UILabel alloc] init]; self.topLabel.translatesAutoresizingMaskIntoConstraints = NO; self.topLabel.numberOfLines = 0; self.topLabel.textAlignment = NSTextAlignmentCenter; self.topLabel.lineBreakMode = NSLineBreakByWordWrapping; self.topLabel.text = @"Some text label. that may have several lines"; self.topLabel.textColor = [UIColor blackColor]; [self.contentView addSubview:self.topLabel]; self.boxView = [[UIView alloc] init]; self.boxView.translatesAutoresizingMaskIntoConstraints = NO; self.boxView.backgroundColor = [UIColor lightGrayColor]; [self.contentView addSubview:self.boxView]; self.bottomLabel = [[UILabel alloc] init]; self.bottomLabel.numberOfLines = 0; self.bottomLabel.textAlignment = NSTextAlignmentCenter; self.bottomLabel.lineBreakMode = NSLineBreakByWordWrapping; self.bottomLabel.translatesAutoresizingMaskIntoConstraints = NO; self.bottomLabel.text = [self bottomLabelText]; self.bottomLabel.textColor = [UIColor blackColor]; [self.contentView addSubview:self.bottomLabel]; [self addContentSubViewConstraints]; } |
在程式碼5 中 我們實現了兩個新方法。一個簡單地返回一個大字串。程式碼6展示 bottomLabelText 方法的實現。
程式碼6: bottomLabelText 實現
1 2 3 4 5 |
- (NSString *)bottomLabelText{ return @"Put in a massive string of your own here to see the scrolling in action"; } |
最後一種方法將新增所有的約束來定義內容檢視的內容高度。程式碼7是 addContentSubViewConstraints 的最終實現,它新增了和IB中所展示的完全相同的約束。
這是相當多的。程式碼7展示了完整的viewController.m檔案。
程式碼7:viewcontoller.m完整檢視
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
#import "ViewController.h" @implementation ViewController - (void)viewDidLoad{ [super viewDidLoad]; self.scrollView = [[UIScrollView alloc] init]; self.scrollView.translatesAutoresizingMaskIntoConstraints = NO; self.scrollView.backgroundColor = [UIColor blueColor]; [self.view addSubview:self.scrollView]; self.contentView = [[UIView alloc] init]; self.contentView.backgroundColor = [UIColor redColor]; self.contentView.translatesAutoresizingMaskIntoConstraints = NO; [self.scrollView addSubview:self.contentView]; //Auto Layout Constraints for scrolling content view NSDictionary *tmpViewsDictionary = @{@"scrollView":self.scrollView, @"contentView":self.contentView}; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[scrollView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(0)-[scrollView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[contentView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(0)-[contentView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1 constant:0]]; [self addContentSubViews]; } - (void)addContentSubViews{ self.topLabel = [[UILabel alloc] init]; self.topLabel.translatesAutoresizingMaskIntoConstraints = NO; self.topLabel.numberOfLines = 0; self.topLabel.textAlignment = NSTextAlignmentCenter; self.topLabel.lineBreakMode = NSLineBreakByWordWrapping; self.topLabel.text = @"Some text label. that may have several lines"; self.topLabel.textColor = [UIColor blackColor]; [self.contentView addSubview:self.topLabel]; self.boxView = [[UIView alloc] init]; self.boxView.translatesAutoresizingMaskIntoConstraints = NO; self.boxView.backgroundColor = [UIColor lightGrayColor]; [self.contentView addSubview:self.boxView]; self.bottomLabel = [[UILabel alloc] init]; self.bottomLabel.numberOfLines = 0; self.bottomLabel.textAlignment = NSTextAlignmentCenter; self.bottomLabel.lineBreakMode = NSLineBreakByWordWrapping; self.bottomLabel.translatesAutoresizingMaskIntoConstraints = NO; self.bottomLabel.text = [self bottomLabelText]; self.bottomLabel.textColor = [UIColor blackColor]; [self.contentView addSubview:self.bottomLabel]; [self addContentSubViewConstraints]; } - (NSString *)bottomLabelText{ return @"Mauris utinam singularis nostrud et vel et defui aliquip duis. Regula suscipere vel ratis damnum in vindico voco verto antehabeo sit bene. Singularis decet capto luptatum sit delenit suscipit aliquip consequat quis nullus ex.Gemino foras te pala consequat refero abbas in vel. Eum nimis commoveo eros eu. Facilisi in pagus gemino exputo quadrum conventio erat. Haero loquor ut quis sudo immitto adsum sit multo proprius esse.Iustum esse si reprobo utrum et vero ad loquor ne. Duis in nulla. Nutus autem brevitas meus iriure verto ullamcorper velit facilisi. Scisco minim damnum quis transverbero eligo nunc nibh tego.Pala vereor uxor ratis macto enim feugiat iustum os delenit. Antehabeo valetudo vel. Neo patria et iaceo nutus. Ut vero veniam ventosus duis consequat verto. Opto neque nonummy. Duis scisco quidne vero nostrud quidne exputo adsum meus qui. Zelus uxor nobis consequat uxor augue decet. Indoles populus consequat iusto et facilisis pecus nunc feugiat vel valde. Delenit sit nisl indoles minim incassum utinam epulae quae euismod dolor tation. Multo ut vero indoles exputo commoveo. Scisco molior tamen ille. Luptatum cogo accumsan luptatum eu fatua usitas. Molior bene elit paratus sed consequat augue veniam probo patria. Nutus quidem feugiat nonummy ad delenit facilisis ea quibus suscipit. Refero utrum torqueo feugait blandit aliquip ad vulputate cui ideo. Nunc vulputate paulatim dolor volutpat vel brevitas. Reprobo iusto vindico. Qui quis commodo augue nostrud nulla eu consequat minim at imputo. Iriure ullamcorper feugait genitus scisco in scisco obruo jus. Consequat abdo quae dignissim iusto suscipere nulla ad jugis duis virtus. Enim vulputate luptatum in voco haero. Feugiat euismod validus sudo uxor abbas. Ingenium obruo neo. Blandit consequat luptatum euismod sino utrum tego suscipit dignissim suscipit. Sed gilvus utrum in capto Velit ventosus adsum delenit et. Vel verto quidem sit qui vulputate ut autem. Accumsan distineo wisi populus hendrerit ne indoles ille facilisis ut erat hendrerit. Populus sino velit premo dolore neque. Augue ulciscor blandit venio facilisi capto quae praesent ad. Vero opto interdico a roto eros abico. Olim eros ad comis incassum wisi consequat dolus molior oppeto in voco. Genitus caecus duis usitas nisl illum suscipit nulla importunus melior autem. Ulciscor tum quia feugiat paratus olim quod quidem. Duis consequat refoveo nulla refoveo nulla wisi nostrud velit. Neque et caecus ne ad occuro nutus diam vulputate. Populus eros quis ne at quia sit luctus. Adipiscing verto olim et virtus luctus nimis foras nisl in eum mos. Imputo saepius lenis reprobo vero. Aliquam probo ea imputo vicis et suscipere. Vulpes iusto imputo dignissim. Dolore aptent feugiat qui et nibh vicis modo abigo. Sit verto minim feugiat nulla praemitto caecus capto lucidus ullamcorper. Fere eu duis facilisi torqueo."; } - (void)addContentSubViewConstraints{ NSDictionary *tmpViewsDictionary = @{@"topLabel":self.topLabel, @"boxView":self.boxView, @"bottomLabel":self.bottomLabel}; [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(60)-[topLabel]-[boxView(86)]-[bottomLabel]-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[topLabel]-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[bottomLabel]-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[boxView]-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.contentView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.boxView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]]; } @end |
1 |
GeSHi Error: GeSHi could not find the language swift (using path /home1/jhoffman/nscookbook/wp-content/plugins/codecolorer/lib/geshi/) (code 2) |
噢, 別忘了 swift,程式碼8 是完整的 swift 版實現。
程式碼8 :完整的swift 控制器
1
|
GeSHi Error: GeSHi could not find the language swift (using path /home1/jhoffman/nscookbook/wp-content/plugins/codecolorer/lib/geshi/) (code 2) |
注意:使用 swift 時我不得不為 content view 定義一個高,這如沒有任何意義一般,因為幾乎是逐字的將程式碼轉換為 swift.目前看來像是一個 swift bug,有任何訊息我會通知你,我猜在 swift 2 時會表現的更好。好了,這期就到這裡了,希望對你有所幫助。