http://oncenote.com/2015/12/08/How-to-build-UI/html
http://www.gameres.com/thread_484046_1_1.html前端
写界面能够说是每位移动应用开发者的基本功,也是一位合格移动应用开发者绕不过去的坎。但就如不是每一位开发者都可以成为合格 的开发者同样,本人在不一样的团队中发现,甚少有人可以编写出合格的UI代码;而很是奇怪的是,在不少的开发者论坛上看到咱们移动 开发者更多关注于某个控件或者是动画,但却不多看到深刻剖析UI机制,指导UI开发的文章。
因为界面涉及到的方面实在过于普遍,本文不可能事无巨细,一一道来,因此本文先立足于点,深刻剖析iOS UI系统中不被重视却很是 重要的机制,帮助本文读者对iOS的UI系统有总体了解;进而以点带面,拓展到UI逻辑设计和架构设计模式的讨论;最后读文而有所思有 所得,设计开发出高效、易用、流畅的UI模块。
程序员
<ignore_js_op>数据库
1. 基础与本质
终端App开发区别于后端开发最大的不一样,就是终端开发很大部分的逻辑是为用户提供界面以供人机交互,即所谓的UI(User Interface) 。因此全部的UI架构主要关注三大模块:界面布局管理,渲染及动画、事件响应;
1.1 布局管理
即在规定的坐标系统上,按照必定的层级顺序位置大小排布在容器内。一个UI系统必然有个基于坐标的布局管理系统,无论是Windows、 Sysbian,仍是Andorid、iOS。好的布局管理机制直接影响界面逻辑实现的难易程度;
咱们如今平常接触到的App的UI坐标系统都是二维的,咱们如今玩的3D游戏,受限于二维的展现屏幕,因此实质上只是三维在二维上的映 射投影。咱们一直在往更高的维度发展:全息影像、Hololens等等。在此能够设想下,将来咱们构建界面的布局管理极可能就是基于真 实三维坐标。
1.2 动画及渲染
UI之因此叫User Interface,就是由于UI经过视觉上的展现,为用户提供信息。这些信息的展现须要经过一系列复杂的计算,最后操做 液晶体展现在显示屏上,这一系列过程就是渲染和动画;
下图就是应用界面渲染到展现的流程:
1.3 事件响应
UI除了展现信息以外,还须要接收并响应用户的点击、手势、摇晃等事件,通过一系列操做后更新展现信息,展现给用户;正确及时地 响应用户的操做并给予反馈,是良好用户体验的保证。为什么Android设备广泛给人的感受比iOS设备要卡,其中一个主要的缘由是iOS系统 将响应用户事件放在主线程的最高优先级。
1.4 UI系统架构
从总体理解了上述三个方面,你会对UI架构有系统认识。iOS中的UI系统架构以下:
后端
<ignore_js_op>设计模式
2. View
UIView是UIKit中最基本控件,就如同NSObject基本上是Cocoa库内全部类的基类同样,UIView也是UIKit中全部界面控件的基类。只要你 愿意,你甚至只用UIView就能够搭建你的App(不过iOS9作了约束,必须设置keyWindow的rootViewControler)。
通常来讲,熟练掌握经常使用的UIView子类控件(如UIButton, UIImageView, UILabel等)就足以应付90%的界面编码须要。但想要编写出高 效、优美的界面代码,还须要更深刻的了解。既然要深刻,本文假设你对UIView已经有了初步的了解,至少使用写过几个完整的页面; 基于此设定下,本文讨论聚焦于如下几点:
1) UIView 与 CALayer:讨论UIView背后的CALayer,了解CALayer与UIView的关系及渲染流程;
2) Offscreen Render:阐述什么是Offscreen Render(离屏渲染),以及一些避免离屏渲染的方法;
3) UIResponser:讨论UIView和UIViewController的父类UIResponser,分析iOS设备上的事件响应链;
4) 设计与实践:结合本人开发实践经验,说明在UIView应用中好的设计实践规则;
参考:View Programming Guide for iOS
2.1 UIView 与 CALayer
咱们应该都知道每一个UIView都包含了一个CALayer,就算你没直接看过CALayer,应该也使用过。好比给一个View切个圆角: view.layer.cornerRadius = 5.0f;;加个边框:view.layer.borderWidth = 1.0f; view.layer.borderColor = [UIColor darkGrayColor].CGColor;,这里使用的layer就是CALayer。
CALayer是QuartzCore库内的类,是iOS上最基本的绘制单元;而UIView只是CALyer之上的封装,更准确的来讲,UIView是CALyer的简版 封装,加上事件处理的集合类。事件处理咱们下一节再讨论,这里的简版封装如何理解,为何不直接使用CALayer?
首先,如上一段所述,CALayer是最基本的绘制单元,每个UIView都有一个CALayer的变量(public var layer: CALayer { get }), UIView的渲染实质就是这个layer的渲染。咱们能够看看的类定义,里面有不少属性(变量)及方法在中能够找到几乎如出一辙的对应; 如属性变量frame、hidden,方法public func convertPoint(p: CGPoint, fromLayer l: CALayer?) -> CGPoint等;但也有更多的属性 方法是UIView所没有的,这里就一一列举了。咱们能够看到UIView实际上是把经常使用的接口(属性和方法)暴露出来了,让UIView更为易用 。
其次,咱们知道iOS平台的Cocoa Touch 是源于OS X平台的Cocoa),是在Cocoa的基础上添加了适用于移动手机设备的手势识别、动画等 特性;但从底层实现上来讲,Cocoa Touch与Cocoa共用一套底层的库,其中就包括了QuartCore.framework;但QuartCore.framework一 开始就是为OS X设计的,因此其中有部分特性是不适合作移动设备开发的,好比最重要的坐标系统。所以,咱们也就不难理解为什么 UIView/NSView在CALayer上作了一层封装。
以上,是UIView于CALayer的主要的关系。
2.2 Offscreen Render
当你尚在懵懂未知的开发初期,在写UIScrollView及其子类(UITableView、UICollectionView)时,必定会遇到滚动不流畅,常常卡顿 的状况;你认真研究代码,发现你逻辑代码都放到了异步线程,主线程作的都是渲染界面的活,为何会卡顿?而后你想老手寻求帮助 ,老手会让你去掉圆角、半透明和阴影之类,App又重回丝般顺滑;你不知道为何,问老手,他可能会很详细跟你解释一通,而后你一 知半解地点点头,脑中一片茫然;较好的状况,也许你依稀记得这么一个词:离屏渲染(Offscreen Render)。那到底什么是Offscreen Render?为何Offscreen Render会致使卡顿?
在第一章的1.2节中有提到渲染的流程图,咱们再更深刻点,先看看最基本的渲染通道流程:
缓存
<ignore_js_op>网络
注:iOS的GPU渲染机制是Tile-Based的,而Tile-Based GPU也是如今移动设备的主流;
咱们再来看看须要Offscreen Render的渲染通道流程:
多线程
<ignore_js_op>架构
通常状况下,OpenGL会将应用提交到Render Server的动画直接渲染显示(基本的Tile-Based渲染流程),但对于一些复杂的图像动画的 渲染并不能直接渲染叠加显示,而是须要根据Command Buffer分通道进行渲染以后再组合,这一组合过程当中,就有些渲染通道是不会直 接显示的;对比基本渲染通道流程和Masking渲染通道流程图,咱们能够看到到Masking渲染须要更多渲染通道和合并的步骤;而这些没 有直接显示在屏幕的上的通道(如上图的 Pass 1 和 Pass 2)就是Offscreen Rendering Pass。
Offscreen Render为何卡顿,从上图咱们就能够知道,Offscreen Render须要更多的渲染通道,并且不一样的渲染通道间切换须要耗费 必定的时间,这个时间内GPU会闲置,当通道达到必定数量,对性能也会有较大的影响;
那哪些状况会Offscreen Render呢?
注:layer.cornerRadius,layer.borderWidth,layer.borderColor并不会Offscreen Render,由于这些不须要加入Mask。
还有更多与Offscreen Render以及动画图形优化相关的知识,请认真观看WWDC。
2.3 设计与实践
以上几节,对View在开发过程当中常常遇到,但并不容易深刻理解的概念进行了讨论。接下来,我想脱离View的具体概念,谈谈本人在 View设计和开发中的一些实践经验;
2.3.1 精简扁平的View层次结构
复杂的View层次结果不只会影响渲染效率,并且也会形成代码的臃肿,会形成不可预料的问题而且难以定位;怎么样维护一个精简扁平 的View层次结构呢?原则以下:
1) 尽可能使用系统原生的控件;
如实现一个icon跟title上下布局的按钮,不少人习惯是使用一个view包含了一个UIButton和一个UILabel。实际上更为推荐的方式是调 整UIButon的contentInset/titleInset/imageInset三个参数来达到这个效果,很是简单,而且title有UIButton上的展现方式和特性, 如能够设置高亮颜色等;
又好比一个有着复杂一点布局结构的滚动界面,有些开发者会以为使用UITableView/UICollectionView实现会比较复杂,有些效果可能 没办法达到,就用他们的基类UIScrollView来实现,本身造了一大套的轮子,代码可能也变得很是复杂;实际上根据个人经验,经过重 写或者是内部属性的调整是彻底可使用UITableView/UICollectionView来达到这个效果,毕竟UITableView/UICollectionView是 UIScrollView的子类,功能不会减小,而会更增强大,而且咱们还能利用已有的data source和delegate机制,实现设计上的解耦。
其余常见的还有UINavigationBar、UITabBar、UIToolBar等等;
2) 合理添加/删除动态View;
有些View是动态的,就是偶尔显示,偶尔隐藏。这类View有两种处理方式:增删,或者显示/隐藏。没有标准的答案,我的更推荐增删的 处理方式,即在有须要的时候添加到对应的ContainerView上,在不须要的时候将其删除。这样便可以与懒加载结合在一块儿,并且也能避 免两个动态View的相互影响,好比TableFooterView,或者是错误加载View。但这并非惟一的方式,假如这个动态View所在的View层级 比较简单,而且须要动画进行动态展现,则使用显示/隐藏也是不错的处理方式。
2.3.2 通用控件;
每个程序员均可以创建本身的代码库,同理,每一位移动开发程序员均可以创建本身的通用控件代码库。这个库内的控件,能够是你 本身写的,也能够是优秀的第三方开源控件。创建控件库,除了可以避免从新造轮子,大大提升咱们的开发效率,还有更为重要的一点 :在运用、改造、重构中掌握接口设计解耦,甚至是架构的知识和经验。
每一个App的UI设计、交互、布局和配色每每千差万别,但总脱离不出移动App这一范畴,也就决定了在某些通用的控件交互上会保持一致 性,以让用户依据本身在移动应用上的使用经验就能轻松快速上手使用,这就是App的移动性。因此通用控件的适用场景每每是很“通用 ”的。好比下拉刷新、加载更多、Tab Bar、提示Tips、加载错误从新加载等等。在新的App或者功能模块上运用这些控件时,你就会思 考怎么让控件更加通用,即不影响旧的逻辑,又可以适用新的需求,这对于作界面的架构设计是很是好的锻炼。
2.3.3 合理运用VC在替代View组合复杂界面;
在界面开发过程当中,咱们经常会遇到复杂的界面,好比多页界面、多种布局方式展现多业务的首页等,但因为很大部分开发者已经对“ 一屏就是一个VC”这一初学者的习惯奉为教条,写出一个庞然大View,再加上复杂的逻辑代码,这一块的代码极可能就演变成了谁都不 敢动的禁区。一个VC能够管理多个VC,因此合理的使用VC来替代View进行复杂界面组合,不只可以将复杂界面切分红更小的粒度,逻辑 代码也同步合理划分,便于维护和重构;而依托VC的机制,还能View和数据的动态加载管理。
下一章中关于轻VC的讨论是这一节知识的拓展。
3. ViewController
上一节关于View的章节已讨论了iOS界面机制,这一节则主要是来谈谈在写界面过程当中的设计问题和基本规范;
ViewController在iOS只是一个很是重要的概念,它是咱们在开发界面时最常打交道的模块,其在一个App中所扮演的角色,View Controller Programming Guide for iOS 中有清晰准确的描述:
1) View Management:管理View;
2) Data Marshalling:管理数据;
3) User Interactions:响应用户交互;
4) Resource Management:管理资源;
5) Adaptivity:适配不一样的屏幕尺寸空间的变化;
能够看到,ViewController有太多的事情要作,这也就致使了ViewController很是容易变得代码膨胀、逻辑混乱等问题;依照我的经验 ,一个ViewController类的有效代码超过500行,这个ViewController就会变得难以维护,但实际上在开发过程当中,每每会遇到上1K行, 甚至2~3K行的ViewController类;当一个ViewController类达到2~3K行,就意味着其余开发者接手这个模块来修改东西,已经没法经过 滚动来定位代码,只能经过搜索;
因此,在进行界面开发时,ViewController须要特别注意模块设计,将不一样的模块按照逻辑进行必定的拆分,即解耦,又防止 ViewController模块的代码膨胀。这就是轻VC的理念;
3.1 轻VC
轻VC是前两年很是火的名词,如今彷佛已经成为了一种业界规范或者是惯例。同上所述,一个VC的类,若是有效代码超过了500行,则表 示这个类看是变得臃肿而难以维护;到达800行,只能经过搜索来定位代码时,重构已势在必行;
关于轻VC,objc.io的开篇第一章#Issue 1 : Lighter View Controllers,足见这一理念的重要性。掌握轻VC的理念基本上是一个iOS开 发者从初级迈向高级必备技能。#Issue 1 : Lighter View Controllers 文中介绍了构建轻VC几种常见的方式:
1) 将数据源等复杂接口从VC中剥离;
2) 把业务逻辑代码抽象到Model层;
3) 将复杂View抽象成独立的类;
4) 使用VC的Containment的特色,将一个VC中逻辑分离的界面模块剥离成为多个子VC;
想要设计出合理而易于理解和维护的轻VC结构,须要掌握轻VC的知识并有必定实践经验。在如下状况下,能够考虑将一个VC设计或者重 构成更多模块更多类的轻VC结构:
1) 如上所述,代码超过500行时;
2) VC内的View的数据源来自多个不一样的地方;
3) VC内有多个复杂的View,须要展现数据实体类较为复杂;
总之,当你感受你的VC已经变得臃肿,那么就可尝试轻VC的实践,实践才有收获。
3.2 VC的设计
相对于View关注于布局和展现,VC更关注设计和管理。本节以一个实例,来简单介绍在一个完整App中的VC设计。
先来看一个常见的UI结构设计例子:
<ignore_js_op>
这个图应该很是容易理解:最底部是一个侧滑抽屉控件,该抽屉包含了App内容展现的TabBarController和设置的VC;TabBarController 的子Item VC包含了相应业务的List VC,点击List VC进入到详情View内;有些详情VC是使用WebViewController来进行内容的展现。非 常简单,不是么?接下来讲明该设计的洞见:
1) Root ViewController,是整个App内Window的根VC,这是一个生命周期与App相同的VC,即Window的RootViewController是惟一且一 直存在的,须要切换场景则使用这个Root VC控制子VC切换来实现(常见于场景:须要进行强登陆,即登陆以后才能使用的App,登陆成 功后从登陆界面切换到主界面,则登陆VC和主界面VC都应该是Root VC的子VC,受Root VC的控制来进行切换)。这个 RootViewController建议是一个UINavigationController,以此保证足够扩展性,并提供更为丰富的界面交互选择。这个Root VC的生命 周期与App一致,这样一些突发的灵活分支界面能够很好的展现在Root VC上,如全局的Loading提示、OpenURL的分支调整等;
2) Main ViewController:主界面,是主要业务展现界面的根界面。该VC与RootVC功能上会很容易重合在一块儿,但须要注意的是,该VC 并不是一直存在,但切换到一些特定分支时,该VC会从Root VC上remove掉,好比前面所说的强登陆App,登陆界面与主界面就会须要进行 切换。另外,该VC隔离了主要业务展现界面的VC与Root VC,便于App总体界面风格的改版和重构。好比如今上图展现的是一个侧滑抽屉 +TabBar的组合,那到下个版本改版把侧滑抽屉去掉,那么只须要使用TabBar替换DrawerMenu VC在Main VC中的位置便可,而不会影响到 RootVC中其余分支展现出来的界面(如Push等)。
3) TabBarItem ViewController:做为TabBar Controller的子Item VC,一般会设计为NavigationController,用以管理各TabBarItem 内的VC栈。
注:若是须要在Push进入二级界面(Detail VC)时隐藏TabBar,只须要设置二级VC的hidesBottomBarWhenPushed = true便可,若是想 更加灵活的控制TabBar,例如进到三级页面的时候显示出TabBar(这个场景应该不多见),或者你的TabBar是自定义的,能够参考我写 的一个开源控件MZNavTab;
本节所示例的UI结构是一个很是通用的UI结构,市面上除游戏外60%以上的App都是相似的UI交互(统计来源于我的手机),假如你的UI 交互与此相似而你的UI结构很混乱的话,不如尝试下这个UI结构设计。
4. MVC、MVP、MVVM
MVC:
<ignore_js_op>
MVP:
<ignore_js_op>
MVVM:
<ignore_js_op>
图注:
虚线箭头:表示二者之间是非强依赖关系。如MVC图,View与Model通常没有直接联系。
虚线矩形:表示该模块在对应架构设计中的隐性存在。即通常性架构中并无这个角色,但立足于iOS这个平台,这又是不可或缺的一部 分;
本文并不打算将MVC、MVP、MVVM这个几个通用架构设计模式的概念通通在这里叙述一遍,上面三个图基本上可以很明白地对比出三者之 间的差别。也许与你在网上看到的不尽相同,这是由于以上三图更立足于iOS平台。
4.1 MVC
咱们最初看到的MVC设计模式图多是这样的:
<ignore_js_op>
而苹果官方给的MVC的设计模式图倒是这样的:
<ignore_js_op>
到底哪一副图才是真正的MVC?个人答案只能是:都是。
MVC从施乐帕克实验室提出至今,已经应用到各类应用开发领域中:Web App能够用MVC,iOS/Android/Windows客户端应用也用MVC,Web 前端也在用MVC,等等;这些几乎涵盖了咱们常见的开发领域,因此MVC其实已经超越了他本来最初的设计,基于全部涉及展现的应用都 能套上MVC,只不过不一样的平台在设计上略有差异。而MVP和MVVM,也不过是MVC的衍生变种,除这二者以外,还有咱们没怎么见过的HMVC 、MVA等。
4.2 Model Layer
在讨论MVP和MVVM以前,我想先明确一个常常被误解的概念:Model。因为Model这个词太通用化,如数据Model,数据库Model,这就致使 了Model这一律念理解差别化,简单的说,就是被玩坏。抛开其余,咱们来看看常见的定义:
Wikipedia的定义:
MSDN(https://msdn.microsoft.com/en-us/library/ff649643.aspx)中的定义:
上面两个定义基本一致:Model,管理应用的行为和数据。
再来看看Apple官方文档Model-View-Controller的定义:
虽然Apple的官方文档是定义Model Objects,但它的含义仍是封装数据以及管理数据相关的逻辑计算;
因此这里须要明确的一个概念是:在MVC的设计模式中,Model是一个Layer,而不仅是一个数据模型(Data Model)类。整体来讲, Model Layer 包含了数据模型,以及管理这些数据相关的逻辑计算,如本地数据变化、数据缓存、从网络请求数据等业务逻辑。关于这 个问题,还能够参考这篇文章:《iOS应用架构谈 view层的组织和调用方案》。但有一点须要说明:该文章更倾向于从Model Object上 思考Model的定义,由于里面的关于Model的示例是从数据模型中扩展出业务接口;而本人则更倾向于从Model Layer来思考Model,即 Model并不限于数据模型,能够是数据管理类(各类Manager)、请求队列管理等等。
4.3 MVP VS MVVM
上一节关于Model Layer中推荐的文章《iOS应用架构谈 view层的组织和调用方案》对MVC和MVVM都作了很是详细的讨论,是一篇很是不 错的文章,推荐各位阅读,那么本节就来讲说MVP,以及我为何更倾向于选择MVP做为App架构设计中的设计框架。
回顾下在本章一开始祭出的MVP以及MVVM两张图,二者之间有什么不一样?
MVVM的VM(View Model)到V(View),比MVP的P(Presenter)到V(View),多了数据绑定。也就是
MVP:是MVC的变种,其中Model和View的定义与MVC的一致,不一样点在于:MVC的Controller是管理一组Model与View之间交互逻辑,是一 个管理者;而Presenter(展现者)则是Model于View之间的链接者,针对特定模块的View提供对应的格式化的Model数据,将View中的行 为反馈到Model中。因此MVC中的Controller通常会管理一个或多个Model和一个或多个View,而Presenter则是 M-P-V 一对一,有更细的 粒度和更好的解耦。
从MVP的定义,你会发现MVP与MVVM极其类似,Presenter与View Model扮演的角色基本没有差异,除了前面所说到绑定机制。但绑定机制 既有很明显的强大优势——自动链接View和Model,也有很明显的缺点——更高的耦合度,更复杂的代码逻辑;但让人感叹命运无常的是 :MVVM随着ReativeCocoa而在iOS平台煊赫一时,而iOS平台上甚少有人说起的MVP,在Android平台却几乎成了标准(Android5.0引入了数 据绑定支持,MVVM会在Android平台有新的发展)。
我为何倾向于MVP?不过是相比于MVVM双向绑定的便利,我更但愿个人App设计中有更强的灵活性和扩展性。没有完美的架构设计模式 ,只有适用于你的App业务场景和团队的设计模式。好比数据逻辑并不复杂、更注重视觉展现的应用,原始的MVC每每是最优解。全部的 MVC衍生出的变种,无非是为了Solve The Problem。
4.4 架构设计模式应用
不管MVC、MVP仍是MVVM,都是指导咱们进行架构设计的模式,并不是能够生搬硬套的;并且在实际的应用中,对于这些设计模式总会有不 同的理解,而且须要根据项目需求进行必要的调整;更为重要的是在咱们App的架构设计中,处理好Model-View-Controller之间的关系 只是基础,最主要的挑战来自于复杂的业务逻辑和场景,这才是体现一个架构师能力所在。
唐巧前不久写的一篇文章《被误解的MVC和被神化的MVVM》对MVC和MVVM的实践的讨论应该是体现了如今移动端主流架构思想,其中对网 络请求层、ViewModel 层、Service 层、Storage 层等其它类的提取设计,才决定了一个App架构设计的优劣。
对于架构设计,我准备在下一篇文章,结合本人在iOS/Android两端的设计经验,作个深刻的讨论,并给出本身的设计范例,供各位讨论 参考。这里先抛出几个在架构设计中最常思考的点,做为下一篇文章的引子:
1) 架构是为了解耦,越松的耦合就表明越多的份层,但人的思惟老是更愿意接受直线思惟,怎么解决这个矛盾?
2) 在一个App中,统一(一致)的架构设计可以让逻辑代码更健壮,更有利于团队成员间的沟通和项目维护,但如何解决其和灵活性之 间的矛盾?
3) 架构设计是否只包含逻辑分层?须要设计数据流和多线程么?
4) 设计模式中的几大原则;
5 总结
以上四个章节,先从UI总体出发,到剖析UIView几点重要机制,接着讨论怎么用好VC这个UI中重要的管理角色,最后则漫谈了 MVC/MVVM/MVP几个架构设计模式的异同和实践应用,想经过以点带面,让咱们在关注了具体实现以后,可以脱离出来,从俯视下咱们App 开发更为总体核心的部分。
相关阅读: