【iOS开发】多屏尺的自动适配 AutoLayout (纯代码方式)

iPhone6分辨率与适配

2014-09-12 10:35 编辑:suiling 分类: iOS开发 来源:CocoaChina
13 42785

(via:sunnyxx's blog

 

分辨率和像素

经新xcode6模拟器验证(分辨率为pt,像素为真实pixel):

1.iPhone5分辨率320x568,像素640x1136,@2x

2.iPhone6分辨率375x667,像素750x1334,@2x

3.iPhone6 Plus分辨率414x736,像素1242x2208,@3x,(注意,在这个分辨率下渲染后,图像等比降低pixel分辨率至1080p(1080x1920)

自动适配

不处理时自动等比拉伸,如果在老工程打印屏幕frame,依然是320x568

对比自动适配的和完美适配的导航栏就能看出问题:

因为拉伸所以会有一些虚,导航栏明显比64要大,但相比3.5寸到4寸的留黑边还是好很多。

 

如何关闭自动适配方案呢?这个还是老思路,换启动图:

除了换启动图外,不得不说的是,新Xcode中可以使用一个xib来设置启动图:

不过这个xib不能关联任何的代码(不能自定义View的Class,不能IBOutlet,不能加Object),可以理解成这个xib就是一张截图,这个方案的好处在于可以使用到Size Classes来针对不同屏幕布局这个xib(感兴趣可以看《Size Classes初探》)

 

关于手动适配

只要手动指定了启动图或者那个xib,屏幕分辨率就已经变成应有的大小了,老代码中所有关于写死frame值的代码通通倒霉,如果去手动适配就要全部适配,建议在找到个可行方案前先不要做修改,自动适配方案还算不影响使用。

 

面对4个分辨率的iPhone,建议使用Auto Layout布局 + Image Assets管理各个分辨率的图片 + Interface Builder(xib+storyboard)构建UI,Size Classes在低版本iOS系统的表现未知。想要这套手动适配方案,起码你的工程需要部署在iOS6+,还不用AutoLayout布局的会死的蛮惨。

 

关于Xcode 6

模拟器路径被换成了 ~/Library/Developer/CoreSimulator/Devices/

xcode6中已经找不到iOS6的模拟器了,是时候说服大家放弃iOS7-了

现在起提交App Store强制需要支持64位,是时候梳理一遍所有依赖的第三方lib,更新到64位。(编辑注:这一条据说是个bug)



关于AutoLayout,最早从iOS6开始引入使用。


主要功能是使用约束,对视图进行相对布局,以适应不同屏尺的变换。

网上大量的资料都在介绍xib和storyboard,如何使用AutoLayout,说纯代码使用AutoLayout进行UI布局的越来越少。对于我这个习惯了代码UI布局的人,写个备忘:

AutoLayout是什么?

使用一句Apple的官方定义的话
AutoLayout是一种基于约束的,描述性的布局系统。 Auto Layout Is a Constraint-Based,Descriptive Layout System.
关键词:
  • 基于约束 -和以往定义frame的位置和尺寸不同,AutoLayout的位置确定是以所谓相对位置的约束来定义的,比如x坐标为superView的中心,y坐标为屏幕底部上方10像素
  • 描述性 - 约束的定义和各个view的关系使用接近自然语言或者可视化语言(稍后会提到)的方法来进行描述
  • 布局系统 - 即字面意思,用来负责界面的各个元素的位置。
总而言之,AutoLayout为开发者提供了一种不同于传统对于UI元素位置指定的布局方法。以前,不论是在IB里拖放,还是在代码中写,每个UIView都会有自己的frame属性,来定义其在当前视图中的位置和尺寸。使用AutoLayout的话,就变为了使用约束条件来定义view的位置和尺寸。这样的 最大好处是一举解决了不同分辨率和屏幕尺寸下view的适配问题,另外也简化了旋转时view的位置的定义 ,原来在底部之上10像素居中的view,不论在旋转屏幕或是更换设备(iPad或者iPhone5或者以后可能出现的miniiPad)的时候,始终还在底部之上10像素居中的位置,不会发生变化。 总结
使用约束条件来描述布局,view的frame会依据这些约束来进行计算 Describe the layout withconstraints, and frames are calculated automatically.

AutoLayout和Autoresizing Mask的区别

AutoresizingMask是我们的老朋友了…如果你以前一直是代码写UI的话,你肯定写过UIViewAutoresizingFlexibleWidth之类的枚举;如果你以前用IB比较多的话,一定注意到过每个view的sizeinspector中都有一个红色线条的Autoresizing的指示器和相应的动画缩放的示意图,这就是AutoresizingMask。在iOS6之前,关于屏幕旋转的适配和iPhone,iPad屏幕的自动适配,基本都是由AutoresizingMask来完成的。但是随着大家对iOSapp的要求越来越高,以及已经以及今后可能出现的多种屏幕和分辨率的设备来说,AutoresizingMask显得有些落伍和迟钝了。AutoLayout可以完成所有原来AutoresizingMask能完成的工作,同时还能够胜任一些原来无法完成的任务,其中包括:
  • AutoLayout可以指定任意两个view的相对位置,而不需要像AutoresizingMask那样需要两个view在直系的view hierarchy中。
  • AutoLayout不必须指定相等关系的约束,它可以指定非相等约束(大于或者小于等);而AutoresizingMask所能做的布局只能是相等条件的。
  • AutoLayout可以指定约束的优先级,计算frame时将优先按照满足优先级高的条件进行计算。
总结
Autoresizing Mask是AutoLayout的子集,任何可以用AutoresizingMask完成的工作都可以用AutoLayout完成。AutoLayout还具备一些AutoresizingMask不具备的优良特性,以帮助我们更方便地构建界面。

AutoLayout基本使用方法

Interface Builder

这部分网上大量的教程,都是说的这个

手动使用API添加约束

创建
iOS6中新加入了一个类:NSLayoutConstraint,一个形如这样的约束
  • item1.attribute = multiplier ? item2.attribute + constant
对应的代码为
1 [NSLayoutConstraint constraintWithItem:button
2                              attribute:NSLayoutAttributeBottom
3                              relatedBy:NSLayoutRelationEqua
4                                 toItem:superview
5                              attribute:NSLayoutAttributeBottom
6                             multiplier:1.0
7                               constant:-padding]
这对应的约束是“button的底部(y)= superview的底部 -10”。
添加
在创建约束之后,需要将其添加到作用的view上。UIView(当然NSView也一样)加入了一个新的实例方法:
  • -(void)addConstraint:(NSLayoutConstraint *)constraint;
用来将约束添加到view。在添加时唯一要注意的是添加的目标view要遵循以下规则:
  • 对于两个同层级view之间的约束关系,添加到他们的父view上
 
  • 对于两个不同层级view之间的约束关系,添加到他们最近的共同父view上
 
  • 对于有层次关系的两个view之间的约束关系,添加到层次较高的父view上
 
刷新
可以通过-setNeedsUpdateConstraints和-layoutIfNeeded两个方法来刷新约束的改变,使UIView重新布局。这和CoreGraphic的-setNeedsDisplay一套东西是一样的~

Visual Format Language 可视格式语言

UIKit团队这次相当有爱,估计他们自己也觉得新加约束的API名字太长了,因此他们发明了一种新的方式来描述约束条件,十分有趣。这种语言是对视觉描述的一种抽象,大概过程看起来是这样的:accept按钮在cancel按钮右侧默认间距处 
 


最后使用VFL(Visual Format Language)描述变成这样:
1 [NSLayoutConstraintconstraintsWithVisualFormat:@\\"[cancelButton]-[acceptButton]\"
2                                         options:0
3                                         metrics:nil
4                                           views:viewsDictionary];
其中viewsDictionary是绑定了view的名字和对象的字典,对于这个例子可以用以下方法得到对应的字典:
1 UIButton *cancelButton = ...
2 UIButton *acceptButton = ...
3 viewsDictionary =NSDictionaryOfVariableBindings(cancelButton,acceptButton);
生成的字典为
{ acceptButton = ""; cancelButton = ""; } 
当然,不嫌累的话自己手写也未尝不可。现在字典啊数组啊写法相对简化了很多了,因此也不复杂。关于Objective-C的新语法,可以参考我之前的一篇WWDC2012笔记: WWDC 2012Session笔记——405 Modern Objective-C 。在view名字后面添加括号以及连接处的数字可以赋予表达式更多意义,以下进行一些举例:
  • [cancelButton(72)]-12-[acceptButton(50)]
    • 取消按钮宽72point,accept按钮宽50point,它们之间间距12point
  • [wideView(>=60@700)]
    • wideView宽度大于等于60point,该约束条件优先级为700(优先级最大值为1000,优先级越高的约束越先被满足)
  • V:[redBox][yellowBox(==redBox)]
    • 竖直布局,先是一个redBox,其下方紧接一个宽度等于redBox宽度的yellowBox
  • H:|-[Find]-[FindNext]-[FindField(>=20)]-|
    • 水平布局,Find距离父view左边缘默认间隔宽度,之后是FindNext距离Find间隔默认宽度;再之后是宽度不小于20的FindField,它和FindNext以及父view右边缘的间距都是默认宽度。(竖线'|‘表示superview的边缘)

容易出现的错误

因为涉及约束问题,因此约束模型下的所有可能出现的问题这里都会出现,具体来说包括两种:
  • Ambiguous Layout 布局不能确定
  • Unsatisfiable Constraints 无法满足约束
布局不能确定指的是给出的约束条件无法唯一确定一种布局,也即约束条件不足,无法得到唯一的布局结果。这种情况一般添加一些必要的约束或者调整优先级可以解决。无法满足约束的问题来源是有约束条件互相冲突,因此无法同时满足,需要删掉一些约束。两种错误在出现时均会导致布局的不稳定和错误,Ambiguous可以被容忍并且选择一种可行布局呈现在UI上,Unsatisfiable的话会无法得到UI布局并报错。对于不能确定的布局,可以通过调试时暂停程序,在debugger中输入
  • po [[UIWindow keyWindow] _autolayoutTrace]
来检查是否存在AmbiguousLayout以及存在的位置,来帮助添加条件。另外还有一些检查方法,来查看view的约束和约束状态:
  • [view constraintsAffectingLayoutForOrientation/Axis:NSLayoutConstraintOrientationHorizontal/Vertical]
  • [view hasAmbiguousLayout]
    • [view exerciseAmbiguityInLayout]

布局动画

动画是UI体验的重要部分,更改布局以后的动画也非常关键。说到动画,CoreAnimation又立功了..自从CA出现以后,所有的动画效果都非常cheap,在auto layout中情况也和collectionview里一样,很简单(可以参考 WWDC2012 Session笔记——219 Advanced Collection Views and Building CustomLayouts ),只需要把layoutIfNeeded放到animationblock中即可~
1 [UIView animateWithDuration:0.5 animations:^{
2     [viewlayoutIfNeeded];
3 }];


部分代码

纯净代码UI正常布局后,添加autolayout就可以了,调整相当方便
这是一段水平居中,垂直并列的4个按钮布局代码
setTranslatesAutoresizingMaskIntoConstraints 是为no,开启AutoLayou.

   //-----autoLayout

   [_btn_1setTranslatesAutoresizingMaskIntoConstraints:NO];

   [_btn_2setTranslatesAutoresizingMaskIntoConstraints:NO];

   [_btn_3setTranslatesAutoresizingMaskIntoConstraints:NO];

   [_btn_4setTranslatesAutoresizingMaskIntoConstraints:NO];

    

   CGSize winSize =[[iHappySDKSingle shareSingle]getScreenSize];

    CGFloattpo =_btn_1.frame.origin.y;

    CGFloathpod =_btn_1.frame.origin.x;

    CGFloatbtnH =_btn_1.frame.size.height;

    CGFloat vpod= winSize.width*0.15-btnH;

    

    NSNumber* tp= [NSNumber numberWithFloat:tpo];

    NSNumber* hd= [NSNumber numberWithFloat:hpod];

    NSNumber* vd= [NSNumber numberWithFloat:vpod];

    NSNumber* bh= [NSNumber numberWithFloat:btnH];

    NSNumber* btm= [NSNumbernumberWithFloat:vpod*2];

    

   NSDictionary *dict1 =NSDictionaryOfVariableBindings(_btn_1,_btn_2,_btn_3,_btn_4);

   NSDictionary *metrics =@{@"hPadding":hd,@"vPadding":vd,@"top":tp,@"btm":btm,@"btnHeight":bh};

   NSString *vfl1 =@"|-hPadding-[_btn_1]-hPadding-|";

[self.viewaddConstraints:[NSLayoutConstraintconstraintsWithVisualFormat:vfl1

  options:0

  metrics:metrics

views:dict1]];

   NSString *vfl2 =@"|-hPadding-[_btn_2]-hPadding-|";

[self.viewaddConstraints:[NSLayoutConstraintconstraintsWithVisualFormat:vfl2

  options:0

  metrics:metrics

views:dict1]];

   NSString *vfl3 =@"|-hPadding-[_btn_3]-hPadding-|";

[self.viewaddConstraints:[NSLayoutConstraintconstraintsWithVisualFormat:vfl3

  options:0

  metrics:metrics

views:dict1]];

   NSString *vfl4 =@"|-hPadding-[_btn_4]-hPadding-|";

[self.viewaddConstraints:[NSLayoutConstraintconstraintsWithVisualFormat:vfl4

  options:0

  metrics:metrics

views:dict1]];

   NSString *vfl5 =@"V:|-(<=top)-[_btn_1(btnHeight)]-vPadding-[_btn_2(btnHeight)]-vPadding-[_btn_3(btnHeight)]-vPadding-[_btn_4(btnHeight)]-(>=btm)-|";

    if(_btn_1.hidden) {

       vfl5 =@"V:|-(<=top)-[_btn_2(btnHeight)]-vPadding-[_btn_3(btnHeight)]-vPadding-[_btn_4(btnHeight)]-(>=btm)-|";

    }

[self.viewaddConstraints:[NSLayoutConstraintconstraintsWithVisualFormat:vfl5

  options:0

  metrics:metrics

views:dict1]];


纯净代码UI正常布局后,增加一个函数,进行自动布局
水平居中布局: NSLayoutAttributeCenterX
垂直居中布局: NSLayoutAttributeCenterY
以及后面的布局切换动画。

-(void)setAutoLayoutForKuang:(UIView*)imgv

{

    UIView * view= self;

    [imgvsetTranslatesAutoresizingMaskIntoConstraints:NO];

   NSDictionary *dict1 =NSDictionaryOfVariableBindings(imgv);

    NSDictionary*metrics =@{@"width":[NSNumbernumberWithFloat:imgv.frame.size.width],

                         @"height":[NSNumbernumberWithFloat:imgv.frame.size.height],

                         @"top":[NSNumbernumberWithFloat:imgv.frame.origin.y]

                         };

   NSString *vfl1 =@"[imgv(width)]";

[viewaddConstraints:[NSLayoutConstraintconstraintsWithVisualFormat:vfl1

  options:0

  metrics:metrics

views:dict1]];

   NSString *vfl2 =@"V:[imgv(height)]";

[viewaddConstraints:[NSLayoutConstraintconstraintsWithVisualFormat:vfl2

  options:0

  metrics:metrics

views:dict1]];

    [viewaddConstraint:[NSLayoutConstraintconstraintWithItem:imgvattribute:NSLayoutAttributeCenterXrelatedBy:NSLayoutRelationEqualtoItem:viewattribute:NSLayoutAttributeCenterXmultiplier:1constant:0]];

    [viewaddConstraint:[NSLayoutConstraintconstraintWithItem:imgvattribute:NSLayoutAttributeCenterYrelatedBy:NSLayoutRelationEqualtoItem:viewattribute:NSLayoutAttributeCenterYmultiplier:1constant:0]];

   //animation

   [UIViewanimateWithDuration:0.25animations:^{

       [imgv layoutIfNeeded];

    }];

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值