一、基本概念app
在iPad和iPhone 5出现以前,iOS设备就只有一种尺寸。咱们在作屏幕适配时须要考虑的仅仅有设备方向而已。而不少应用并不支持转向,这样的话就彻底没有屏幕适配的工做了。随着iPad和iPhone 5,以及接下来的iPhone 6的推出,屏幕尺寸也变成了须要考虑的对象。在iOS7以前,为一个应用,特别是universal的应用制做UI时,咱们总会首先想咱们的目标设备的长宽各是多少,方向变换之后布局又应该怎么改变,而后进行布局。iOS6引入了AutoLayout来帮助开发者使用约束进行布局,这使得在某些状况下咱们再也不须要考虑尺寸,而能够专一于使用约束来规定位置。iphone
既然咱们有了AutoLayout,那么其实经过约束来指定视图的位置和尺寸是没有什么问题的了,从这个方面来讲,屏幕的具体的尺寸和方向已经不那么重要了。可是实战中这还不够,AutoLayout正如其名,只是一个根据约束来进行布局的方案,而在对应不一样设备的具体状况下的体验上还有欠缺。一个最明显的问题是它不能根据设备类型来肯定不一样的交互体验。不少时候你仍是须要判断设备究竟是iPhone仍是iPad,以及如今的设备方向到底是竖直仍是水平来作出判断。这样的话咱们仍是难以完全摆脱对于设备的判断和依赖,而以后若是有新的尺寸和设备出现的话,这种依赖关系显然显得十分脆弱的(想一想要是有iWatch的话..)。ide
因此在iOS8里,Apple从最初的设计哲学上将原来的方式推翻了,并引入了一整套新的理念,来适应设备不断的发展。这就是SizeClasses。布局
再也不根据设备屏幕的具体尺寸来进行区分,而是经过它们的感官表现,将其分为普通(Regular)和紧密(Compact)两个种类(class)。开发者即可以无视具体的尺寸,而是对这这两类和它们的组合进行适配。这样不论在设计时仍是代码上,咱们均可以再也不受限于具体的尺寸,而是变成遵循尺寸的视觉感官来进行适配。字体
SizeClasses有三个值:Regular,Compact和Any。Any是什么意思呢?若是weight设为Any,height设置为Regular,那么在该状态下的界面元素在只要height为Regular,不管weight是Regular仍是Compact的状态中都会存在。这种关系应该叫作继承关系,具体的四种界面描述与可继承的界面描述以下:动画
1
2
3
4
|
w:Compacth:Compact继承(w:Anyh:Compact,w:Compacth:Any,w:Anyh:Any)
w:Regularh:Compact继承(w:Anyh:Compact,w:Regularh:Any,w:Anyh:Any)
w:Compacth:Regular继承(w:Anyh:Regular,w:Compacth:Any,w:Anyh:Any)
w:Regularh:Regular继承(w:Anyh:Regular,w:Regularh:Any,w:Anyh:Any)
|
这么多设备(iPhone 4S,iPhone 5/5s,iPhone 6,iPhone 6Plus,iPad,AppleWatch)的尺寸,就经过SizeClasses简单的表达出来了:ui
iPhone4S,iPhone 5/5s,iPhone 6atom
竖屏:(w:Compacth:Regular)spa
横屏:(w:Compacth:Compact)设计
iPhone6Plus
竖屏:(w:Compacth:Regular)
横屏:(w:Regularh:Compact)
iPad
竖屏:(w:Regularh:Regular)
横屏:(w:Regularh:Regular)
AppleWatch(猜想)
竖屏:(w:Compacth:Compact)
横屏:(w:Compacth:Compact)
PS:附上图形:
二、UITraitCollection和UITraitEnvironment(Size Classes手写代码)
为了表征SizeClasses,Apple在iOS 8中引入了一个新的类,UITraitCollection。这个类封装了像水平和竖直方向的SizeClass等信息。iOS 8的UIKit中大多数UI的基础类(包括UIScreen,UIWindow,UIViewController和UIView)都实现了UITraitEnvironment这个接口,经过其中的traitCollection这个属性,咱们能够拿到对应的UITraitCollection对象,从而得知当前的SizeClass,并进一步肯定界面的布局。
和UIKit中的响应者链正好相反,traitCollection将会在viewhierarchy中自上而下地进行传递。对于没有指定traitCollection的UI部件,将使用其父节点的traitCollection。这在布局包含childViewController的界面的时候会至关有用。在UITraitEnvironment这个接口中另外一个很是有用的是-traitCollectionDidChange:。在traitCollection发生变化时,这个方法将被调用。在实际操做时,咱们每每会在ViewController中重写-traitCollectionDidChange:或者-willTransitionToTraitCollection:withTransitionCoordinator:方法(对于ViewController来讲的话,后者也许是更好的选择,由于提供了转场上下文方便进行动画;可是对于普通的View来讲就只有前面一个方法了),而后在其中对当前的traitCollection进行判断,并进行从新布局以及动画。代码看起来大概会是这个样子:
override func willTransitionToTraitCollection(newCollection: UITraitCollection, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator){ super.willTransitionToTraitCollection(newCollection, withTransitionCoordinator: coordinator) coordinator.animateAlongsideTransition({ (context: UIViewControllerTransitionCoordinatorContext!) -> Void in if (newCollection.verticalSizeClass == UIUserInterfaceSizeClass.Compact) { //To Do: modify something for compact vertical size } else { //To Do: modify something for other vertical size } self.view.setNeedsLayout() }, completion: nil) }
在两个To Do中,咱们应该删除或者添加或者更改不一样条件下的AutoLayout约束(固然,你也能够干其余任何你想作的事情),而后调用-setNeedsLayout来在上下文中触发转移动画。若是你坚持用代码来处理的话,可能须要面临对于不一样SizeClasses来作移除旧的约束和添加新的约束这样的事情,能够说是很麻烦(至少我以为是麻烦的要死)。可是若是咱们使用IB的话,这些事情和代码均可以省掉,咱们能够很是方便地在IB中指定各类SizeClasses的约束(稍后会介绍如何使用IB来对应SizeClasses)。另外使用IB不只能够节约成百上千行的布局代码,更能够重新的Xcode和IB中获得不少设计时就能够实时监视,查看而且调试的特性。能够说手写UI和使用IB设计的时间消耗和成本差距被进一步拉大,而且出现了不少手写UI没法实现,可是IB能够不假思索地完成的任务。从这个意义上来讲,新的IB和SizeClasses系统能够说无情地给手写代码判了个死缓。
另外,新的API和体系的引入也同时给不少咱们熟悉的UIViewController的有关旋转的老朋友判了死刑,好比下面这些API都弃用了:
@property(nonatomic, readonly) UIInterfaceOrientation interfaceOrientation - willRotateToInterfaceOrientation:duration: - willAnimateRotationToInterfaceOrientation:duration: - didRotateFromInterfaceOrientation: - shouldAutomaticallyForwardRotationMethods
如今所有统一到了viewWillTransitionToSize:withTransitionCoordinator:,旋转的概念再也不被提倡使用。其实仔细想一想,所谓旋转,不过就是一种Size的改变而已,咱们都被Apple骗了好多年,不是么?
三、InterfaceBuilder中使用SizeClasses
建立一个新的通用项目。若是你想要早在一个已经建立了的Xcode6项目,你须要激活sizeclasses选项。你能够在InterfaceBuilder中的属性面板勾选autolayout的选项的下面找到它。
首先,让咱们在Xcode中看一下sizeclass的网格。这是一个你能够在不一样的布局排列间切换的区域。当你查看storyboard的时候,看到视图的底部,而且点击‘wAnyhAny’字样的标签。你将会看到一些相似网格的画面。
默认的,咱们以一个基础的设置开始,也就是anywidth和anyheight。不少事情都将在这里安置和改变,包括了iphone和ipad的全部方向的默认布局。苹果建议把大多数的设置都在这个界面中进行设置。这个是由于减小工做量而显得特别的简单。让咱们布局一个超级宽的按钮在画面的中间。给它一个绿色的背景,从而让咱们看到它真实的尺寸,给它一个约束来让他居中。
而且给它一个夸张的固定宽度600。
好了,如今在ipad和iphone的模拟器都运行一下,你将会看到都是居中,但对于iphone的两个方向都太宽了,(这里你设置了页面中button的宽度但并无立刻更新是由于你在作添加约束的时候没有更新图形,致使了以下图的状况,storyboard里面没有更新,而在模拟器运行时候更新了,左边大纲栏目里面也有警告说明,能够直接点击警告里面的黄色三角来更新画面其实就是UpdataFrame)
让咱们使用sizeclasses来修正吧。回到刚才那个第一张图的网格选择iphone的纵向(portrait)设置,就是紧凑的宽度+常规的高度。网格中的红色矩形.
你将会注意到你在网格中选中以后底部的bar改变为蓝色。那是在警告你:“Hey,你并非在一个基础的设置,有些改变将会只在你运行的时候显示。因此这个bar如今是蓝色的!”我所说的一些改变是由于有四项你能改变的sizeclasses:1约束常数,2字体,3约束的开/关,4子视图的开/关。
前两个是不言而喻的,可是让我来告诉你如何让后二者工做。在当前的sizeclass(compactwidth和regularheight)情况下让咱们试着把一个约束关闭。在文档的提纲栏里,点击设置在咱们的button的CentreX校准约束:
在看一下咱们的属性检查栏,在底部咱们能够看到带标记的一个单词“Installed”,而且左侧有额外的加号按钮。点击额外的加号而且点选'CompactWidth|RegularHeight'(当前的就是)。
如今你将会看到2个标记物,把刚刚添加的哪个取消勾选(wChR)
如今咱们的约束再也不安置而且作任何事情来配置sizeclasses。就像你看到的,Xcode正在控诉咱们的约束太混乱了(左边的大纲会有错误提示表示你缺乏了约束-译者),若是你这时候运行app在iphone的模拟器上的话,按钮不在X方向居中了。可是在ipad的上面仍是居中的,由于约束仍然安置在基本的设置里面。这个约束将会一直配置着除非咱们把它取消勾选。你甚至可以旋转你的iphone模拟器,而且发现button将会神奇的回到居中,由于iphone的横向是不一样的sizeclass配置,好了,让咱们把勾选回来,让button回到居中。
如今让咱们改变咱们设置在button宽度的约束,选择button,而且来到Size的属性检查栏,下拉到底部,咱们能够看到全部的约束。点击Width本来是600的使用Edit设置为100:
在iPhone的模拟器上运行,你将会看到button已经具有了正确的宽度。运行在ipad的模拟器的时候却展现了600的宽度,由于咱们没有改变基本设置里面的宽度。可是,在iphone的横向landscape仍然看着不怎么样,由于iphone的横向设置来自基本的AnyAny的设置。让咱们修正一下。在网格里面咱们选择compactWidth和CompactHeight。也就是第一张图的蓝色网格。
如今咱们在这个设置下改变width的约束,就像咱们为了compactxregular改变的同样。给予一个400的宽度。运行一下iphone的模拟器,而且旋转到横向,按钮有了400的宽度,看上去很棒。达到了咱们的预想。有一点很好就是你能看到一个全部的约束的列表,这些都是不一样的设置的。仅仅选择你想要在文档大纲里面看到的约束,而后来到属性检查栏,他们整齐的排列在初始的常数下面。它标注了每个基于它所应用的设置。
即便咱们决定咱们想要只在iphone横向landscape模式下button消失,使用sizeclasses咱们只要反向安置views就像咱们反向安置一个约束。选择咱们的UIbutton,滚动到属性检查器的底部。经过点击加号按钮给咱们当前的设置添加一个新的安置选项,而后取消勾选它。
就像你看到的,那个view立马消失了,由于咱们在设置里面反向安置了它,咱们立马就能看到。运行app,你能看到它在纵向的portraitiphone上消失了,可是当你旋转到横向的landscape的时候又回来了。固然它也一直安置在ipad上面由于ipad仍然使用的是基本的设置。
四、SizeClasses和ImageAsset及UIAppearence
ImageAsset里也加入了对SizeClasses的支持,也就是说,咱们能够对不一样的SizeClass指定不一样的图片了。在ImageAsset的编辑面板中选择某张图片,Inspector里如今多了一个Width和Height的组合,添加咱们须要对应的SizeClass,而后把合适的图拖上去,这样在运行时SDK就将从中挑选对应的Size的图进行替换了。不只如此,在IB中咱们也能够选择对应的size来直接在编辑时查看变化。
实际作起来实在是太简单了..但拿个demo说明一下吧,好比下面这个实现了竖直方向Compact的时候将笑脸换成哭脸--固然了,一行代码都不须要
另外,在iOS7中UIImage添加了一个renderingMode属性。咱们可使用imageWithRenderingMode:并传入一个合适的UIImageRenderingMode来指定这个image要不要以Template的方式进行渲染。在新的Xcode中,咱们能够直接在ImageAsset里的RenderAs选项来指定是否是须要做为template使用。而相应的,在UIApperance中,Apple也为咱们对于SizeClasses添加了相应的方法。使用+appearanceForTraitCollection:方法,咱们就能够针对不一样trait下的应用的apperance进行很简单的设定。好比在上面的例子中,咱们想让笑脸是绿色,而哭脸是红色的话,不要太简单。首先在ImageAsset里的渲染选项设置为TemplateImage,而后直接在AppDelegate里加上这样两行:
UIView.appearanceForTraitCollection(UITraitCollection(verticalSizeClass:.Compact)).tintColor=UIColor.redColor()
UIView.appearanceForTraitCollection(UITraitCollection(verticalSizeClass:.Regular)).tintColor=UIColor.greenColor()