为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新功能的进一步了解,会开始尝试在自己的项目中使用。


0 0
原创粉丝点击