本文由CocoaChina译者 SolarDreams 翻译
原文: iOS Programming Recipe 36: A Fixed Width Dynamic Height ScrollView in AutoLayout
作为一个iOS 开发者,很多情况下会需要把一个和屏幕等宽的 contentview 添加到一个 scollrview 内部中。大多数的 app 需要有响应式布局,所以使用 Autolayout 可谓明智之选。第一次学习 scrollviews 时,你或许会觉得碉堡了。但它们奇怪的规则也会令人有点沮丧。
你对 Xcode 和 interface builder 都足够熟悉。
这篇文章重点在 autolayout,所以我要用各种方式来告诉你如何处理它。第一个方式是 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 上所有的子视图必须要有一个高度。一些子视图可以使用它们固有的高度,通常是由它们的宽来决定的。一般地,你需要为任何没有包含文本的视图指定宽度和高度。这些包含文本的视图至少有一个指定的宽或 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 时会表现的更好。好了,这期就到这里了,希望对你有所帮助。