PPPan's 平凡之路

非宁静无以致远

做一个互联网内容的贡献者。


Storyboard的爱与恨(上)

尽管现在已经是Apple将Storyboard整合进Xcode中的第四个年头,大家对于Storyboard的评价仍然褒贬不一。有早期就选择转向Storyboard用于UI开发的国内业界领头人物,也有创建项目就立马删除Storyboard的大牛。我经历过纯代码布局,同时也在多个多人合作项目中使用Storyboard开发界面。在初期绕过各种坑后,Storyboard将会是快速构建UI界面的好帮手,特别是在现如今设备分辨率与尺寸日益增加的情况下,它可以帮助工程师们节约大量的界面代码书写时间。Storyboard存在的一大意义在于为UI提供了可视化开发方式,另一方面提供了一种更好的MVC的View层实现方式,让你的ViewController代码更简洁。当然,Storyboard的不足仍然不可忽视,错误的难以定位经常让刚上手的开发者们手足无措,相比于代码更不容易阅读的XML源文件所导致多人合作中的冲突不易解决等问题仍然有待完善。本文从各个方面介绍一下Storyboard,分享一下Storyboard的一些使用心得。

历史

1986年Jean-Marie Hullot发明了IB(Interface Build--Storyboard的前身),并且和Macintosh的工具箱无缝融合,这一工具被Denison Bollay发现了。第二年, Denison Bollay带着Hullot和他的IB到NeXT,将IB演示给Steve Jobs看。老乔立意识到了IB的价值,并将其纳入到了NeXTSTEP中。之后Steve 带着NeXT的技术结晶(当然也包括IB)重新回归Apple,并将之整合到了Apple的体系中。2008年第一代iPhone SDK发布的时候,IB就已经捆绑在其中。到了Xcode4,Apple更是直接将其集成进IDE里。随后随着不断地改进,更新,演变,最终变成了我们今天所看到的Storyboard。从某种角度来说,Storyboard也是老乔留给我们的众多礼物之一。

故事板能做什么

故事板主要为我们提供了以下的功能:(这些功能都是可视化的)

  • Auto Layout
  • Size Classes
  • Secnce的跳转
  • 代码可视化

Auto Layout

自动布局颠覆了之前直接操作Frame的布局方式,从思考View应该在哪个位置,变成了考虑在特定条件下,View的所处的位置需要满足哪些条件。通过这些条件来确定View的Frame。自动布局在实际应用中大体上可以将分为三组:

View与Super View的约束

View自身的约束

View与Other View的约束

假如我们需要在代码中使用自动布局可以使用 Visual Format Language或者NSLayoutConstraint的简单工厂方法来生成约束,然后添加到View上。我们来看一个例子:

//用代码来实现上图中View与Super View的约束
    UIView *superView = self.view;
    UIView *subView = [[UIView alloc] init];
    NSLayoutConstraint *leadingConstraint = [NSLayoutConstraint constraintWithItem:superView
                                                           attribute:NSLayoutAttributeLeading
                                                           relatedBy:NSLayoutRelationEqual
                                                              toItem:subView
                                                           attribute:NSLayoutAttributeLeading
                                                          multiplier:1
                                                            constant:15];
    NSLayoutConstraint *TrailingConstraint = [NSLayoutConstraint constraintWithItem:superView
                                                                         attribute:NSLayoutAttributeTrailing
                                                                         relatedBy:NSLayoutRelationEqual
                                                                            toItem:subView
                                                                         attribute:NSLayoutAttributeTrailing
                                                                        multiplier:1
                                                                          constant:15];

//topConstraint init...
//bottomConstraint init...
    [superView addConstraint:leadingConstraint];
    [superView addConstraint:TrailingConstraint];
    [superView addConstraint:topConstraint];
    [superView addConstraint:bottomConstraint];

    // 如果是iOS8+ 则使用下面的方式来激活Constraint
    // leadingConstraint.active = YES;
    // leadingConstraint.active = YES;
    // leadingConstraint.active = YES;
    // leadingConstraint.active = YES;

是不是一大团乱糟糟的代码?Visual Format Language用起来更加令人崩溃。好在业界已经有比较好的代码自动布局的第三方解决方案。但是仍然会有大堆的简单界面布局代码残留在你的代码中。

为了让你的生活更轻松(也为了让代码更清爽),Storyboard就包含了非常优雅的可视化自动布局解决方案。以上一切,在Storyboard中都被浓缩成了两个按钮(下图红圈中的椭圆按钮)。

  • 红框1:为被选中View和离他最近的View(可能是SuperView,也可能是另一个同层级的View,看哪个离它更近)添加Leading、Training、Top、Bottom四个属性约束。
  • 红框2:为View添加自身宽和高约束
  • 红色椭圆左侧按钮:当选中多个View时,为多个View添加约束

只需要点击几下鼠标,Storyboard就可以帮你轻松完成视图布局。

Auto Layout Debug

使用代码来对Auto Layout布局的另一个缺点在于debug的困难。当添加了多余的约束,往往只能在运行时才能发现错误。同时,要寻找出是哪一行代码添加了错误的约束也比较费力(往往连控制台都没有错误输出)。

而Storyboard却为此提供了非常友好的静态检查。主要针对View的约束、布局提供警告和Error,甚至是解决方案。

上图的例子是:我们为Label添加了多余的约束,Storyboard用红色标记出冲突的约束,并给出修改建议:删除其中一个约束以保证约束的正确性。是不是很友好? :)

Size Classes

Apple 与iOS 8推出了Size Classes的概念。意在解决因设备尺寸造成的适配问题。Size Classes通过将界面的宽度和高度抽象为正常紧凑两种概念,通过合理的组合,可以将现有设备(以及未来将要出现的设备)划分到不同的Size中。因此,无论是代码还是界面布局,只需要针对Size进行,而不用再拘泥于分辨是iPhone还是iPad,是横屏还是竖屏的问题了。Size Classes的推出是具有前瞻性的,无论是Apple Watch还是iOS 9推出的的iPad 分屏模式,都可以用Size Classes完美解决适配的问题。

Size Classes和现有设备的对照表如下:

在之前,我们要对横屏竖屏的界面进行区分,代码一般是这样的:

if (IPAD_PORTRAIT)  
{
    //TODO:modify something portrait
}
else  
{
    //TODO:modify something landscape
}

在Size Classes时代,Apple引入了一个新的类UITraitCollection来封装水平和垂直方向的Size信息。现在我们通过代码来改变界面是这样的:

- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection 
              withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator
{
    [super willTransitionToTraitCollection:newCollection 
                 withTransitionCoordinator:coordinator];
    [coordinator animateAlongsideTransition:^(id <UIViewControllerTransitionCoordinatorContext> context) 
    {
        if (newCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact) {
            //To Do: modify something for compact vertical size
        } else {
            //To Do: modify something for other vertical size
        }
        [self.view setNeedsLayout];
    } completion:nil];
}

在TODO中做相对应Size的事。

可以想见的是,仍然会有非常多的布局代码占据着你的源文件。但在Storyboard中,一切变得异常简单。

使用Size Classes,我们只需要选择相对应的size,在那个Size下进行布局。运行时,就会根据设备的尺寸,自动地展示相对应Size的布局。比如iPhone竖屏就展示width Compact height RegularSize下的信息。当手机横屏,系统会自动添加一个过渡动画(虽然有点生硬),并转到width Regular height Compact的Size。这一切不需要一行代码。

能不能再给力点?

Sure.有这么一种情景:iPhone横屏下,拥有一个avatarView,竖屏下拥有一个相同的avatar View。这种情况下我们只需要在一个Size中完成这个View,然后在Storyboard的attributed inspector中做一些勾选,将其"install"进相对应的Size中,就可以达到复用的目的。如果有差异,则在对应的Size中定制即可。(如下图)

能不能再给力点儿?

Of Course!除了View,约束也可以不同Size配置不同。最厉害的是,图片文件也可以根据Size来区分。我们只需要对.xcassets文件勾选Size Classes,就可以为不同Size配置不同图片.这意味着,在同一个安装包下,通过Size Classes,我们甚至可以为横屏iPhone和竖屏iPhone做出完全不同的App!

Storyboard的爱与恨(下)