为iPhone6设计自适应布局(纯代码实现)
来源:互联网 发布:最优化理论与方法 编辑:程序博客网 时间:2024/05/19 22:03
目前网络上已经有很多关于AutoLayout的讲义可供大家学习,大部分的Demo都是通过IB或者Storyboard上完成的。很多人也在思考,到目前iOS 8这个版本,使用代码来实现UI布局是不是合适?今天有时间,使用纯代码写了一小段布局代码,供大家比较。
本文所需要实现的界面布局来自这一篇博客:ADAPTIVE LAYOUTS FOR iPHONE 6,对应的中文翻译版本为:为iPhone6设计自适应布局。读者可以先行阅读以上这篇博客,来了解AutoLayout和Size Class的基本概念,跟着博客中的步骤,使用Storyboard完成上述Demo。
需求
本文Demo需要同时在iPhone 4, iPhone 5, iPhone 6 和 iPhone 6 Plus完成以下布局
并且支持横竖屏旋转,旋转动画如下:
本文的示例代码可以在Github上直接获取。
分析
通过参考图,我们可以发现需要实现的页面布局中:最外层是一个NavigationController。内容区域,在竖屏情况下,把个人信息从上到下布局,在横屏情况下,从左到右布局。
内容区域代码实现
1) 初始化控件
- (void)loadView{ [super loadView]; self.avatarImageView = [self createAvatarImageView]; [self.view addSubview:self.avatarImageView]; self.nameLabel = [self createNameLabel]; [self.view addSubview:self.nameLabel]; self.timeLabel = [self createTimeLabel]; [self.view addSubview:self.timeLabel]; self.descriptionLabel = [self createDescriptionLabel]; [self.view addSubview:self.descriptionLabel]; self.photoImageView = [self createPhotoImageView]; [self.view addSubview:self.photoImageView];}- (UIImageView *)createAvatarImageView{ UIImageView *avatarImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"avatar"]]; avatarImageView.translatesAutoresizingMaskIntoConstraints = NO; return avatarImageView;}- (UILabel *)createNameLabel{ UILabel *nameLabel = [[UILabel alloc] initWithFrame:CGRectZero]; nameLabel.font = [UIFont systemFontOfSize:12.0f]; nameLabel.translatesAutoresizingMaskIntoConstraints = NO; nameLabel.text = @"Chun Tips"; return nameLabel;}- (UILabel *)createTimeLabel{ UILabel *timeLabel = [[UILabel alloc] initWithFrame:CGRectZero]; timeLabel.font = [UIFont systemFontOfSize:11.0f]; timeLabel.translatesAutoresizingMaskIntoConstraints = NO; timeLabel.text = @"2w ago"; return timeLabel;}- (UILabel *)createDescriptionLabel{ UILabel *descriptionLabel = [[UILabel alloc] initWithFrame:CGRectZero]; descriptionLabel.font = [UIFont systemFontOfSize:11.0f]; descriptionLabel.translatesAutoresizingMaskIntoConstraints = NO; descriptionLabel.text = @"Apple, Google, Microsoft, Instagram, Twitter, Facebook and 4 others like this"; descriptionLabel.numberOfLines = 0; return descriptionLabel;}- (UIImageView *)createPhotoImageView{ UIImageView *photoImageView = [[UIImageView alloc] initWithFrame:CGRectZero]; photoImageView.translatesAutoresizingMaskIntoConstraints = NO; photoImageView.image = [UIImage imageNamed:@"photo"]; return photoImageView;}
以上代码,和以往手写代码布局基本雷同。有三点需要注意的地方:
- 使用AutoLayout布局时初始化控件的frame应该为 CGRectZero
- 对于使用AutoLayout布局的控件应该设置translatesAutoresizingMaskIntoConstraints为 NO
- 如果需要让UILabel在AutoLayout中正确的支持多行显示,需要设置numberOfLines==0(默认为1),并且在适当的地方设置 preferredMaxLayoutWidth 这个值
2) 初始化控件好了之后,我们就应该开始让控件支持自动布局:
- (void)updateConstraintsForTraitCollection:(UITraitCollection *)collection{ //等待实现}- (vodi)loadView{ //... //上面已经实现过的代码 [self updateConstraintsForTraitCollection:self.traitCollection];}
需求中我们需要支持iPhone设备的横竖两个方向,在Size Class概念中,UITraitCollection包含了我们所需要的信息。所以我们依据当前ViewController的traitCollection来构造不同的布局约束。
在设备的traitCollection改变时(旋转),我们可以在willTransitionToTraitCollection方法中捕获到相应信息,并开始做旋转动画:(关于该方法的详细描述,可以参考上一篇博客)
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator{ [super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator]; [self updateConstraintsForTraitCollection:newCollection]; [coordinator animateAlongsideTransition:^(id <UIViewControllerTransitionCoordinatorContext> context) { [self updateConstraintsForTraitCollection:newCollection]; [self.view setNeedsLayout]; } completion:nil];}
3)好了,最后一步,我们开始实现布局代码。
3.1 初始化我们在布局约束中需要的所有Views和一些常量值,放到字典里。初始化需要更新的constraits,放到数组里。
CGFloat const kLayoutPadding = 10.0f; // 每个控件之间的间距为10.0f- (void)updateConstraintsForTraitCollection:(UITraitCollection *)collection{ NSDictionary *views = @{@"topLayoutGuide": self.topLayoutGuide, @"avatarImageView": self.avatarImageView, @"nameLabel": self.nameLabel, @"timeLabel": self.timeLabel, @"descriptionLabel": self.descriptionLabel, @"photoImageView": self.photoImageView}; NSDictionary *metrics = @{@"padding": @(kLayoutPadding)}; NSMutableArray *updateConstraits = [NSMutableArray array];}
上面提到的topLayoutGuide属性,在当前的场景下,它对应的NavigationBar,高度也为NavigationBar的高度。有不知道的朋友可以参考之前写过的一篇博客,有详细叙述。
3.2 从Size Class的文档中,我们可以查询到,iPhone在竖屏情况下,水平方向是 Compact, 竖直方向是 Regular,而在横屏情况下,两个方向都是 Compact。从我们的设计稿的需求,我们可以通过判断竖屏方向的Size Class来进行分别约束:
- (void)updateConstraintsForTraitCollection:(UITraitCollection *)collection{ //... //上面已经实现过的代码 if (collection.verticalSizeClass == UIUserInterfaceSizeClassCompact) { // 横屏情况 } else { // 竖屏情况 } if (self.constraits) { [NSLayoutConstraint deactivateConstraints:self.constraits]; } self.constraits = updateConstraits; [NSLayoutConstraint activateConstraints:self.constraits];}
完成布局约束之后,使用activateConstraints激活布局约束。如果之前有旧的布局约束,应该先使用deactivateConstraints移除。
3.3 实现约束代码:
// 横屏 [updateConstraits addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|[photoImageView]-(padding)-[avatarImageView]-(padding)-[nameLabel]" options:0 metrics:metrics views:views]]; [updateConstraits addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"[avatarImageView]-(padding)-[timeLabel]" options:0 metrics:metrics views:views]]; [updateConstraits addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"[photoImageView]-(padding)-[descriptionLabel]" options:0 metrics:metrics views:views]]; [updateConstraits addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topLayoutGuide][photoImageView]|" options:0 metrics:metrics views:views]]; [updateConstraits addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topLayoutGuide]-(padding)-[avatarImageView]-(padding)-[descriptionLabel]" options:0 metrics:metrics views:views]]; [updateConstraits addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topLayoutGuide]-(padding)-[nameLabel]-(padding)-[timeLabel]" options:0 metrics:metrics views:views]]; self.descriptionLabel.preferredMaxLayoutWidth = self.view.bounds.size.width - self.view.bounds.size.height + self.topLayoutGuide.length - 2 * kLayoutPadding; //label在横屏下能显示的最大宽度 = 屏幕宽度 - 图片宽度(屏幕高度 - NavigatioBar的高度) - label本身显示的左右间距。 // 竖屏 [updateConstraits addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(padding)-[avatarImageView]-(padding)-[nameLabel]" options:0 metrics:metrics views:views]]; [updateConstraits addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"[avatarImageView]-(padding)-[timeLabel]" options:0 metrics:metrics views:views]]; [updateConstraits addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|[photoImageView]|" options:0 metrics:nil views:views]]; [updateConstraits addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(padding)-[descriptionLabel]" options:0 metrics:metrics views:views]]; [updateConstraits addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topLayoutGuide]-(padding)-[avatarImageView]-(padding)-[photoImageView]-(padding)-[descriptionLabel]" options:0 metrics:metrics views:views]]; [updateConstraits addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topLayoutGuide]-(padding)-[nameLabel]-(padding)-[timeLabel]" options:0 metrics:metrics views:views]]; self.descriptionLabel.preferredMaxLayoutWidth = self.view.bounds.size.width - 2 * kLayoutPadding; //label在竖屏下能显示的最大宽度 = 屏幕宽度 - label本身显示的左右间距。 // 共享代码 [updateConstraits addObject:[NSLayoutConstraint constraintWithItem:self.avatarImageView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.avatarImageView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0]]; [updateConstraits addObject:[NSLayoutConstraint constraintWithItem:self.photoImageView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.photoImageView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0]];
上述布局代码中,有两点需要特别提出:
- 需要支持多行显示的descriptionLabel应该在不同界面显示的情况下更新preferredMaxLayoutWidth。
- 共享代码是让图片在横竖屏的情况下实现宽高等比(需求是1:1,显示为正方形)。
总结
上述通过纯代码实现了和这篇博客一样的Demo:ADAPTIVE LAYOUTS FOR iPHONE 6。不知道有兴趣的朋友在使用过两个方式实现UI布局之后,有什么感悟,觉得哪一种更加适合现在的生产开发。
在写这篇博客之前,在公司项目中,还一直保留着纯代码使用AutoLayout实现UI的方法。通过最近对Storyboard和IB新功能的进一步了解,会开始尝试在自己的项目中使用。
- 为iPhone6设计自适应布局(纯代码实现)
- 为iPhone6设计自适应布局(纯代码实现)
- 为iPhone6设计自适应布局(一)
- 为iPhone6设计自适应布局(一)
- 为iPhone6设计自适应布局(二)
- 为iPhone6设计自适应布局(一)
- 为iPhone6设计自适应布局(二)
- 为iPhone6设计自适应布局(一)
- 为iPhone6设计自适应布局(二)
- 为iPhone6设计自适应布局(一)
- 为iPhone6设计自适应布局(二)
- 为iPhone6设计自适应布局(一)
- 为iPhone6设计自适应布局(二)
- 为iPhone6设计自适应布局(二)
- 为iPhone6设计自适应布局
- iPhone6设计自适应布局
- 演示:纯CSS实现自适应布局表格
- 为iPhone 6设计自适应布局(一)
- 2.14 文件操作
- 正确的 Composer 扩展包安装方法
- objective-c与c字符串互相转换
- 克服时间这个魔鬼
- 3.1 函数的定义与调用
- 为iPhone6设计自适应布局(纯代码实现)
- 3.2 参数传递、返回值及函数声明
- 职场感悟
- 3.3 全局变量和局部变量
- 使用Entify Framework 6.x的事务操作
- 3.4 函数调用机制
- Java 完成部分水吧点饮品系统的:点饮品,饮品管理片段的实现
- IOS加密方式
- list的三个实现类的区别