【iOS】利用PureLayout实现:比例自动布局(AutoLayout)

早在前两年还没有iPhone6(s)、iPhone6(s) plus出现的时候,在开发iOS的应用程序的时候感觉在布局上都完爆Android,因为虽然iPhone5与iPhone4的高度不一样,但是参照宽度(320px)都是一样的。
  但是在iPhone6(s)、iPhone6(s) plus出现后,iPhone的屏幕分辨率开始碎片化,但是使用iOS的AutoLayout好像也是屌屌的,完全能够针对屏幕大小进行相应的自动布局(相对布局)。
  但是眼尖的程序员可能会发现:在iPhone5上的控件如果设置了边距,字体大小等等,在iPhone6Plus上也是一样一样的,并没有像Android上会按照屏幕的分辨率比例缩放。这是因为在Android开发过程中并没有使用像素单位px,而是使用的pd为单位,而字体使用的sp为单位。

对比图.png

这是iPhone6p与iPhone6的对比图,几乎肉眼可见(本人用测量工具侧量过),加入按钮高度是一样的。

其实这样的布局也不会带来什么特别的困扰,只要设计师考虑了iOS布局的这个特性,设计得当,最后出来的效果无非是iPhone6p出来的效果空一些,而iPhone5出来的效果经凑一些。
  一直以来我也是按照这种设计模式开发自己的App。


然而命运出现了转折......
  在新的一个项目中,设计师设计的图纸是在1242px的画布(参考的iPhone6p的尺寸)上设计的。按照iPhone6p的屏幕尺寸切图额和标注给我后,导致出来的结果是在iPhone6p上效果上佳,但是在iPhone5上就开始挤在一起,甚至出现了有些控件叠加在一起。
  这就是因为iOS开发布局后字体大小、间距、控件大小并不会按照屏幕的分辨率等比例缩放的结果。
  然后设计师又重新按照iPhone6的尺寸标注了一份新的设计图后,开发出来的结果也是并不理想,因为在iPhone6上表现上佳,但是iPhone5上紧了,在iPhone6p上又显得比较空了。

对比图2.png
问题所在:

最后我发现,大部分设计师在设计的时候,工作流程都是这样:
1.在自己的参照的屏幕尺寸下设计;
2.导出设计图,放在iPhone6p、iPhone6、iPhone5等设备上查看效果;
3.效果感觉满意后就可以开始标注、切图了;

看着好像并没有什么问题,但是问题恰恰就出现在第2步,导出图片放在手机中查看,如果宽度和手机屏幕宽度一致,高度按比例缩放了,那么其实图片里面展示的字体大小、控件间距、控件大小都随着该比例缩放了。但是iOS开发中并没有进行缩放的操作,这也就成为了为什么设计师明明看着好好的,可是出来的效果怎么就出现偏差了呢?

最后设计师问我,能不能按照屏幕分辨率直接等比例缩放,这样在所有的屏幕上就全部都完美了。

对比图3.png

参照上图的意思是说,在iPhone6p上这个itemView能够展示2.5个,那么在iPhone6上面也是2.5个,而不会因为iPhone6的屏幕宽度小了就只展示2个。


说了这么多,也应该开始正真的代码开发了。

先放两张效果图看看:

对比图4.png
对比图5.png

效果显示不管是字体大小、控件间距、控件大小都跟随屏幕大小自适应缩放大小,然后终于合了设计师的心意了~

我给我的这个操作起了个名字:比例自动布局(AutoLayout)。使用的AutoLayout第三方库是PureLayout,这里就不再对PureLayout做过多的介绍了,不知道的同学可以在github上搜索下载学习。
  大部分操作也是在PureLayout的源文件里进行修改操作。

1.选取参考尺寸

因为自己是从iPhone4、iPhone5开发过来的,原来的很多设计图,都是参考这个尺寸做的,所以我选择的参考尺寸是320px,当然你也可以选择其他的参考尺寸,比如iPhone6的375px。

在UIView扩展类:ALView+PureLayout.m中添加:

//依据iPhone5的尺寸得到当前屏幕相对于iPhone5屏幕尺寸的大小
+(CGFloat)sizeFromIphone5:(CGFloat)size{
    #if TARGET_OS_IPHONE
    static float width = 0;
    if (width == 0) {
        width = [UIScreen mainScreen].bounds.size.width;
    }
    return width/320.0*size;
    #endif
    return size;
}

//依据真实的尺寸得到iPhone5屏幕尺寸的大小
+(CGFloat)sizeFromRealSize:(CGFloat)size{
#if TARGET_OS_IPHONE
    static float width = 0;
    if (width == 0) {
        width = [UIScreen mainScreen].bounds.size.width;
    }
    return 320.0/width*size;
#endif
    return size;
}
2.字体大小修改

对UIFont扩展方法:

#import "UIFont+XM.h"

@implementation UIFont (CW)

+(UIFont *)cwFontWithSize:(CGFloat)fontSize{
    return [UIFont systemFontOfSize:[UIView sizeFromIphone5:fontSize]];
}

+(UIFont *)cwBoldFontWithSize:(CGFloat)fontSize{
    return [UIFont boldSystemFontOfSize:[UIView sizeFromIphone5:fontSize]];
}

@end

在开发中全部字体设置都用这个方法来执行。

3.控件大小

在传入size后,对size进行加工:

size = [UIView sizeFromIphone5:size];
- (NSLayoutConstraint *)autoSetDimension:(ALDimension)dimension toSize:(CGFloat)size relation:(NSLayoutRelation)relation
{
    size = [UIView sizeFromIphone5:size];
    
    self.translatesAutoresizingMaskIntoConstraints = NO;
    NSLayoutAttribute layoutAttribute = [NSLayoutConstraint al_layoutAttributeForAttribute:(ALAttribute)dimension];
    NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self attribute:layoutAttribute relatedBy:relation toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:size];
    [constraint autoInstall];
    return constraint;
}
4.控件间距

在传入offset后,对offset进行加工:

offset = [UIView sizeFromIphone5:offset];

因为不管是两个控件之间的相对间距,还是相对于父控件的间距,在PureLayout中最后执行都是这个方法:

- (NSLayoutConstraint *)autoConstrainAttribute:(ALAttribute)attribute toAttribute:(ALAttribute)toAttribute ofView:(ALView *)otherView withOffset:(CGFloat)offset relation:(NSLayoutRelation)relation
{
    offset = [UIView sizeFromIphone5:offset];
    
    self.translatesAutoresizingMaskIntoConstraints = NO;
    NSLayoutAttribute layoutAttribute = [NSLayoutConstraint al_layoutAttributeForAttribute:attribute];
    NSLayoutAttribute toLayoutAttribute = [NSLayoutConstraint al_layoutAttributeForAttribute:toAttribute];
    NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self attribute:layoutAttribute relatedBy:relation toItem:otherView attribute:toLayoutAttribute multiplier:1.0 constant:offset];
    [constraint autoInstall];
    return constraint;
}
5.对抗系统自带大小

iOS有很多自带的布局控件,而且大小(特别是高度)是固定的,我的操作是:

1.UINavigationBar:全部隐藏,就当没有这个控件,全部自定义;
2.UITabBar:因为并没有什么影响,所以不予置理;

6.心理

为什么会把这个单独提出来,原因是开发中需要调整自己的心理影响,现在的做法是目前的屏幕宽度都是320px。

比如在设置UILabel的preferredMaxLayoutWidth属性的时候:

-(void)layoutSubviews{
    [super layoutSubviews];
    contentLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.frame)-[UIView sizeFromIphone5:10]-[UIView sizeFromIphone5:26];
    commentLabel.preferredMaxLayoutWidth = CGRectGetWidth(commentView.frame)-[UIView sizeFromIphone5:22];
}

比如在得到屏幕5等分宽度的时候:

float width = 320/5;
7.总结

设计师工作流程就变成了这样:
1.在自己的参照的屏幕尺寸下设计;
2.导出设计图,放在iPhone6p、iPhone6、iPhone5等设备上查看效果;
3.效果感觉满意后就可以开始在【960px或者640px或者320px上进行标注】,因为可能直接在320px上标注像素会丢失,最好在960px上标注,开发人员自行除以3处理;
4.1242px上切图,因为要保证图片的分辨率在大屏手机上的清晰度;

可能还有很多没有考虑到(涉世未深,工作经验并不多),这里提出的只是一个参考的思路,本人对PureLayout的使用也是刚刚接触,其中的方法,我常用的也就这么几个,如果用到了其他的方法,肯定是要注意的。

推荐阅读更多精彩内容