作者 | 钱凯

杏仁移动开发工程师,前嵌入式工程师,关注大前端技术新潮流。

前端开发的屏幕适配其实算是基本功,每个码农在长期实践中都有自己的总结。

在 iOS 平台上,苹果爸爸对适配的支持个人感觉很不人性化,提供了 AutoLayout、sizeClass 等技术,感觉没有前端类似 flexBox 这样的技术来得灵活。像是点歪了技能树,过于重视使用 xib 配置 UI,但很多码农还是习惯纯代码编程。Cocoa 没有 css 这样的纯布局文件,导致很多时候我们将布局、UI 和逻辑写在一起,十分混乱、冗长。

下面简单介绍下在实践中适配屏幕的方向思路,抛砖引玉。

从设计到代码:沟通与标准

App 的 UI 界面是由设计人员(产品,UI)绘制的,然后由开发实现,双方要有良好的沟通,并且把设计内容标准化、文档化。

对设计方来说,适配的规则总是在设计师心中的,是按比例的缩放,还是固定的间距,是公用一套规则,还是在大屏下有特殊的布局,都需要有明确方式传达给耿直的码农们。

一般常见的布局方式有:

固定间距:在不同尺寸下,间距总是固定。

流式布局:文字,图片等在不同屏幕下流式排布,比如大屏下一行显示四张图片,小屏一行三张,图片尺寸固定。

比例放大:间距,文字大小,图片大小等比例放大。

保持比值:两个UI元素或者图片的长宽等属性保持一定的比值。

对齐:元素间按某个方向对齐。

设计师需要将这些布局规则标注清楚,有利沟通,也方便日后追溯。

对于一些通用 UI 组件,要进行标准化,设计上有利于 App 风格统一,实现上也方便开发进行封装。

UI 的搭建:xib VS 纯代码

苹果一直用 xib 来标榜他们家 App 开发简单易上手:将各种你需要的东西往屏幕上一拖一放,一个 UI 界面就搞定了,这很 cool 不是嘛!

Xib 的优点显而易见:

易上手、可视化,所见即所得

减少代码量

快,适合小 App 快速开发

但是在我们的实际项目中,是不推荐使用 xib 的。

首先,xib 本身过于笨拙,只能搭建一些简单的 UI,动态性很差,难以满足 App 复杂的 UI 交互需求。

其次,做过性能优化的同学都知道,xib(or StoryBoard)的性能是很差的,相对于用纯代码 alloc 的组件来说,xib 加载慢,而且会占用 App 包的体积。不仅仅是 App 的性能,使用老 mac 打开较大的 xib 文件,有时候会卡的你怀疑人生,严重影响开发效率(心情)。

除此以外,对于团队协作来说,xib 也不是一个好选项:阅读困难,无法在 git 上查看历史改动,容易造成冲突,造成冲突后难以解决,元素通过 outlets 与代码的链接难以维护,容易在改动中造成错漏等等。

另外,对于我这种中途转到前端的工程师来说,对一切在 IDE 界面上配置的东西都有种迷之不信任,感觉不如一行行黑底白字的代码来的靠谱。

当然我们不是完全禁用了 xib,用代码码 UI 的缺点也很明显:繁琐,代码量大。因此对一些元素较多,又比较固定的 UI 组件,我们可以用 xib 来减少代码量:

针对UI代码繁琐,重复编码多的情况,我们可以通过适当封装(UI 工厂类),组织结构(MVC,分离 UI 代码)等手段,清晰逻辑。

// label 工厂方法

  • (UILabel *)labelWithFont:(UIFont *)font
    color:(UIColor *)
    text:(NSString *)text
    attributeText:(NSAttributeString *)attributeText
    alignment:(NSTextAlignment)alignment;

布局:返璞归真

从 iOS7 开始苹果在 Cocoa 平台引入 AutoLayout 进行 UI 的基本布局。但是 AutoLayout 非常反人类,不仅代码繁琐而且使用不灵活限制很多。

比如我想要把三个元素等间距地展示在屏幕上,用 AutoLayout 写完基本蛋都碎了,更别说动态地在两套布局间切换这种高级需求。

后来苹果推出 sizeClass,试图解决多套布局的问题,但是仍然没有触及到码农的痛点,而且依赖 xib 使它泛用性不好。

一段典型的 AutoLayout 代码如下所示:

_topViewTopPositionConstraint = [NSLayoutConstraintconstraintWithItem:_topInfoViewattribute:NSLayoutAttributeToprelatedBy:NSLayoutRelationEqualtoItem:self.viewattribute:NSLayoutAttributeTopmultiplier:1.0constant:self.navigationController.navigationBar.frame.size.height + self.navigationController.navigationBar.frame.origin.y];[self.view addConstraint:topViewLeftPositionConstraint];(这里省略上述类似结构*4)

上面省略了很多代码,实际上一页都放不下。它干了什么呢,只是将一个元素紧贴屏幕上边缘放置。项目中我们会使用三方 AutoLayout 的封装:PureLayout ,简化代码,也有其它实用功能。

AutoLayout 比较适合:

基本的对齐(上下左右对齐,居中对齐等)

固定的布局,固定的间距,动态性不高的页面

简单且数量较少的 UI 元素

不擅长:

比例布局

动态性较强的页面局部

不同屏幕大小比例的适配

复杂的 UI

另外有一点,AutoLayout 对性能是有损耗的,所以对性能有要求的场景,比如列表中的 cell,我们会用代码计算 frame,提高滑动帧率。

所以在实际工程中,需要来选择布局方式。

下面是 App 中首页新闻 Feeds 的布局代码片段:

  • (void)layoutSubviews {

    [super layoutSubviews];

    CGFloat cellWidth = CGRectGetWidth(self.bounds);
    CGFloat currentY = 0.f;

    // 0.content
    CGFloat cellHeight = CGRectGetHeight(self.bounds);
    CGFloat contentHeigth = cellHeight - kCellPaddingHeight;
    _mainContentView.frame = CGRectMake(0, 0, cellWidth, contentHeigth);

    // 1. topic
    CGFloat topicLabelWidth = [_topicLabel.text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_topicLabel.font} context:nil].size.width;

    CGFloat topicLabelHeight = [@“测高度” boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_topicLabel.font} context:nil].size.height;

    CGFloat topicLogoLeftPadding = 3.f;
    CGFloat topicLogoWidth = 10.f;
    CGFloat topicLeftPadding = 13.f;

    _topicView.frame = CGRectMake(topicLeftPadding, currentY + kTopicUpPadding, topicLogoWidth + topicLogoLeftPadding + topicLabelWidth, topicLabelHeight);
    _topicLogo.frame = CGRectMake(topicLabelWidth + topicLogoLeftPadding, CGRectGetHeight(_topicView.frame) / 2.0 - topicLogoWidth / 2.0, topicLogoWidth, topicLogoWidth);
    _topicLabel.frame = CGRectMake(0, 0, topicLabelWidth, topicLabelHeight);

    (省略大量代码……)

    // 10._sourceLabel
    CGSize sourceSize = [_sourceLabel.text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_sourceLabel.font} context:nil].size;

    _sourceLabel.frame = CGRectMake(kEdgeHorizontalPadding, currentY + kLeadingUpPading, sourceSize.width, sourceSize.height);
    }

可以看到,为了确定每个元素的位置,我们需要进行大量的计算,代码可读性也不好,繁琐难读。如果引入动态性,比如不同屏幕字体大小改变,元素大小按比例扩大等,则计算量又要上一个数量级。

动态布局:清晰独立

UI 界面是动态的,在不同状态,不同尺寸或者手机的横竖屏情况下,我们往往需要在多套布局方案中切换,或者对布局进行微调。如果使用 xib 布局的话,可以使用 SizeClass + AutoLayout 的方案;如果是代码实现的页面,则没有官方提供的工具,只能用逻辑去判断。

一般来说,我们写复杂的 UI 页面,需要遵循两个原则:

UI 布局代码要清晰:这是最重要的,要一眼就知道在调整那一块,怎么调整,如果不能,适当拆分,优化命名。

布局代码要和业务逻辑独立:在一些常用设计模式下,我们会将 UI 和数据模型解耦,在 UI 内部,同样要将交互,配置这些逻辑和布局解耦,独立出类似前端 css 这样的纯布局文件。

将布局代码提炼出来,在不同尺寸下调用不同的实现:

if (IS_IPHONE_6){
self.layout = [MyLayout iPhone6Layout];
}else if (IS_IPHONE_6_PLUS){
self.layout = [MyLayout iPhone6PlusLayout];
}

// 实现小屏幕布局

  • (MyLayout *)iPhone6Layout {…}
    // 实现大屏幕布局
  • (MyLayout *)iPhone6PlusLayout {…}

字体适配:字体集

在开发中我们经常会遇到需要动态设置字体的情况:

不同屏幕尺寸,或者横竖屏,需要展示不同的字体大小。

为用户提供了文章调节字体选项。

App 的不同语言版本,需要显示的字体不一样。

较为简单的做法是用宏或者枚举定义字体参数,针对不同尺寸的屏幕,我们拿到不同的值:

#ifdef IPHONE6
#define kChatFontSize 16.f
#else IPHONE6Plus
#define kChatFontSize 18.f
#endif

在对一些旧代码做字体适配扩展的时候,直接修改源码改动太多,容易混乱,可以采用 runTime 方法 hack Label 等控件的展示,替换原有的 setFont 方法:

  • (void)load{

    Method newMethod = class_getClassMethod([self class], @selector(mySystemFontOfSize:));
    Method method = class_getClassMethod([self class], @selector(systemFontOfSize:));
    method_exchangeImplementations(newMethod, method);
    }

  • (UIFont *)mySystemFontOfSize:(CGFloat)fontSize{
    UIFont *newFont=nil;
    if (IS_IPHONE_6){
    newFont = [UIFont adjustFont:fontSize * IPHONE6_INCREMENT];
    }else if (IS_IPHONE_6_PLUS){
    newFont = [UIFont adjustFont:fontSize * IPHONE6PLUS_INCREMENT];
    }else{
    newFont = [UIFont adjustFont:fontSize];
    }
    return newFont;
    }

以上套路缺点显而易见:不够灵活,将逻辑分散,不便于维护,扩展性也不好。

一种比较好的实践是引入字体集(Font Collection)的概念,什么是字体集呢,我们在用 Keynote 或者 Office 的时候,软件会提供一些段落样式,定义了段落、标题、说明等文字的字体,我们可以在不同的段落样式中切换,来直接改变整个文章的字体风格。

听上去和我们的需求是不是很像呢,我们在代码中也是做类似的事情,将不同场景下的字体定义到一个 Font Collection 中:

@protocol XRFontCollectionProtocol

  • (UIFont *)bodyFont; // 文章
  • (UIFont *)chatFont; // 聊天
  • (UIFont *)titleFont; // 标题
  • (UIFont *)noteFont; // 说明

    @end

不同的场景,灵活选择不同的字体集:

  • (id)currentFontCollection {

#ifdef IS_IPhone6
return [self collectionForIPhone6];
#elif IS_IPhone6p
return [self collectionForIPhone6Plus];
#endif
return nil;
}

// set font
titleLabel.font = [[XRFontManager currentFontCollection] titleFont];

适配新的屏幕或者场景,我们只需要简单地增加一套字体集就好了,可以很方便的管理 App 中的字体样式,做动态切换也很简单。

总结来说,用代码在一个尺寸实现设计稿是比较简单的,但是要在各种尺寸下忠实反应设计的想法需要合理的代码设计以及一定的代码量。

UI 的还原其实也是大前端开发非常重要的部分,作为程序员,往往重视代码的稳定,业务的正常使用而忽略软件界面这个同样重要的用户体验因素。设身处地地想,如果设计看到自己精心调配的比例、字体、色号在不同尺寸手机上显示得歪七倒八,一定会气的要死吧。

原文链接

https://mp.weixin.qq.com/s?__biz=MzUxOTE5MTY4MQ==&mid=2247483758&idx=1&sn=182579552711d0c70f159b21cc95e0c7&scene=21#wechat_redirect

服务推荐

  • 蜻蜓代理
  • 代理ip
  • 微信域名拦截检测
  • 微信域名检测api

iOS 屏幕适配浅谈相关推荐

  1. IOS 屏幕适配(一)理论篇

    IOS 屏幕适配(一)理论篇 1. IOS 屏幕适配基本概念 1.1 IOS 设备的尺寸和分辨率 1.1.1 分辨率相关概念 1.1.2 IOS 各个设备对应的分辨率 1.2 设计和开发之间的多屏适配 ...

  2. IOS 屏幕适配理论篇

    @[TOC](IOS 屏幕适配(一)理论篇) 1. IOS 屏幕适配基本概念 1.1 IOS 设备的尺寸和分辨率 1.1.1 分辨率相关概念 点(Points): 是iOS开发中引入的抽象单位,称作点 ...

  3. IOS屏幕适配(四)最新系统IOS13适配

    IOS屏幕适配(四)最新系统IOS13适配 3. IOS 最新系统适配问题 3.1 IOS 13 适配 3.1.1 即将废弃的 LaunchImage 3.1.2 Sign in with Apple ...

  4. iOS 3D Touch浅谈

    一.什么是3D Touch? 3D Touch是iPhone 6s推出的一种可以让你与手机进行互动的全新方式.除了轻点.轻扫.双指开合这些熟悉的 Multi‑Touch 手势之外,3D Touch 还 ...

  5. iOS屏幕适配-iOS笔记

    学习目标 1.[了解]屏幕适配的发展史 2.[了解]autoResizing基本用法 3.[掌握]autoLayout 的基本用法 4.[掌握]autoLayout代码实现 5.[理解]sizeCla ...

  6. iOS屏幕适配方案-Auto Layout

    市场上的android手机五花八门.各种尺寸的屏幕让android程序员们比較头疼. 也有一些大神写了一些博客提出了自己的观点.iOS貌似也迎来了大屏6+,因此屏幕适配的问题也是有滴,因此苹果也有自己 ...

  7. html5开发之ios屏幕适配,iOS开发屏幕尺寸以及屏幕适配等问题(转载内容)

    原帖地址:http://blog.csdn.net/phunxm/article/details/42174937/ 仅供我个人收藏学习,原博主如不同意请联系qq651263878进行删除,在此表示感 ...

  8. (转)iOS 屏幕适配

    参考 微信的多屏适配 目前为止,iPhone屏幕尺寸已经有四种: 3.5(inch):1/3G/3GS/4/4S 4.0(inch):5/5S/5C 4.7(inch):6 5.5(inch):6Pl ...

  9. iOS 屏幕适配 iPhone X SafeArea安全区域

    三月份工作 9月份才开始自己主动了解安全区域适配问题 一. 前言 本文的出发点是对iOS设备的适配, 我们之前的适配只是考虑设备的尺寸, 设备的方向, 而在iPhoneX出来之后呢, 我们又多了一种考 ...

最新文章

  1. Struts2 学习系列 (3) 跳转类型与通配符映射
  2. java maven mvn clean package 打包执行流程
  3. MTK Android 编译命令
  4. Eos离线密钥生成的PHP代码
  5. .NET 之路 | 007 详解 .NET 程序集
  6. run在java_Java语言start和run方法的区别
  7. JAVA实现把指定文件夹下的所有文件压缩成zip包
  8. 贵大计算机考研情侣,贵大计算机研究生怎么样?
  9. “如何学习”系列文章2007年全部文章索引
  10. git diff生成patch用法
  11. Vue学习(二):class与style绑定
  12. 思科网院Packet Tracer实验(八)子网划分
  13. wps表格宏被禁用如何解禁_wps宏被禁用如何打开?
  14. 2.Windows 界面技术发展现状
  15. 【大数据分析】Spark SQL查询:使用SQL命令
  16. 南方cass快捷键命令修改在哪_南方cass快捷键大全_南方cass快捷键命令大全_好特教程...
  17. 跨站请求伪造(CSRF)漏洞简介及靶场演示
  18. 差分贴片晶振使最强军事武器出世
  19. 爬虫爬取到百度首页html,python爬虫实战之爬取百度首页的方法
  20. 学术论文科研写作方法总结--针对深度学习,自然语言处理等领域

热门文章

  1. 企业电子邮件营销策略(Email营销策略)
  2. 更改aspx页面编码格式
  3. 【Camera】手机相机自动对焦的3种方式及原理
  4. Windows远程桌面实现之六(新版本框架更新,以及网页HTML5音频采集通讯)
  5. 什么软件测试苹果手机循环电池,如何检查iPhone电池的电池循环次数,看完你就明白了...
  6. 论文笔记:Visual Domain Adaptation with Manifold Embedded Distribution Alignment
  7. Jenkins的全量迁移
  8. linux系统弹出鼠标,Ubuntu14.04及以上操作系统鼠标闪烁问题
  9. VMware 虚拟机鼠标闪烁
  10. 户外设备选择远距离蓝牙需要了解的知识-----工程师必看