本文由CocoaChina譯者 SolarDreams 翻譯
原文:iOS Programming Recipe 36: A Fixed Width Dynamic Height ScrollView in AutoLayout
作為一個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 設置屬性
@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:在主視圖中添加滾動視圖和內容視圖
- (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的約束
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 方法
- (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 實現
- (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 實現
- (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完整視圖
#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
噢, 別忘了 swift,代碼8 是完整的 swift 版實現。
代碼8 :完整的swift 控制器
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 時會表現的更好。好了,這期就到這裡了,希望對你有所幫助。