为iOS设计:图形和性能
在之前的文章里,我们探讨了基于多种不同技术来实现自定义的UIButton,当然不同的技术所涉及到的代码复杂度和难度也不一样。但是我也有意提到了基于不同方法的实现所体现出的性能表现也不一一相同。
【在屏幕背后的东西】
为了了解性能是如何受到影响的,我们需要进一步地观察iOS里图形实现背后的一些内容。下面这张图呈现了不同的frameworks和libraries之间的一些联系:
在最顶层的就是UIKit,一个在iOS中用来管理用户图形交互的Objc高级的框架,它由一系列的集合类构成,例如UIButton、UILabel,每一个都负责他们指定的UI Control角色。UIKit本身构建在一个叫Core Animation的框架之上,它因为被用于处理更为强大的平滑的转场效果而引入OS X Leopard和iOS而出名。
再往下深入一点就是OpenGL ES了,一个用在移动设备上绘制2D和3D计算机图形的标准开源库,它广泛地被用在游戏的图形绘制上,同样在Core Animation和UIKit中发挥着强大的作用。
最后一部分就是Core Graphics,曾经在Quartz(一个基于CPU的绘制引擎,在OS X系统上初次露脸)中被引入。这两个较为底层的框架都是用C语言编写的。
上图中的最底下一行是硬件层,由GPU和CPU组成。
我们经常说到的硬件加速其实是指OpenGL,Core Animation/UIKit基于GPU之上对计算机图形合成以及绘制的实现,直到目前为止,iOS上的硬件加速能力还是大大领先与android,后者由于依赖CPU的绘制,绝大多数的动画实现都会让人感觉明显的卡顿。
而离屏绘制(Offscreen drawing)的话就是指GPU一边在当前屏幕上进行绘制,而另一边在屏幕还没有处理图像信息之前通过CPU来生成图像信息的处理过程。在iOS当中,离屏绘制在以下的情况下会自动触发:
* Core Graphics(任何以CG开头的类)
* 在drawRect方法里,甚至是空方法实现
* 所有shouldRasterize属性是YES的CALayers对象
* 所有用了masks(setMasksToBounds)和动态阴影的(setShadow*)的CALayers对象
* 所有文字的绘制,包括CoreText
* Group opacity(UIViewGroupOpacity)
总地来说,当有涉及到动画的时候离屏绘制就会影响到性能,你可以通过Instruments进行真机调试,从而检测到底是哪部分UI正在进行离屏绘制:
1.接入设备
2.在XCode的Developer Applications里打开Instruments(Command+Shift+i)
3.选择iOS>Graphics>Core Animation template
4.打开详情面板,选择适当的窗口模式
5.选择你的target设备
6.检查Color Offscreen-Rendered Yellow的debug选项
7.在你设备上所有的离屏绘制都会呈现出黄色的色调
现在让我们逐一检查上一篇文章里涉及的一些技术点的性能表现。
【预资源加载绘制】
使用UIImage来加载原先就在磁盘中的图片作为自定义UIButton的背景图片的话,完全依赖与GPU的绘制。而且如果用可resizable的图片,也可适当避免当背景大小动态变换的时候需要不同大小的图片,这样可以让我们App的bundle做得更小,而且在绘制像素的图的时候也重复地利用了硬件加速。
【使用CALayer】
因为基于CALayer实现的方法里我们使用到了遮罩去绘制圆角的效果,所以这里会涉及到离屏绘制。当我们使用Core Animation框架的时候,如果上面提到的用遮罩绘制圆角的属性是开启的话,我们也必须明确地禁止使用动画效果。作为底线,除非你非要进行一个动画转场效果,否则这种方法并不适用。
【使用drawRect】
drawRect方法依赖Core Graphics框架来进行自定义的绘制,但这种方法主要的缺点就是它处理touch事件的方式:每次按钮被点击后,都会用setNeddsDisplay进行强制重绘;而且不止一次,每次单点事件触发两次执行。这样的话从性能的角度来说,对CPU和内存来说都是欠佳的。特别是如果在我们的界面上有多个这样的UIButton实例。
【技巧混合 hybrid approach】
这样来说的话,是不是就意味着使用预资源加载进行绘制就是唯一可行的方案了呢?答案是否定的。如果你坚持需要用代码来灵活地进行绘制的话,还是有一些优化代码还有减少性能消耗方面的技巧的。有一种可行的方案就是创建stretchable的位图并在多个实例对象之间进行重用。
我们按照在之前的教程的相同步骤创建一个新的UIButton的子类,然后如下定义一些静态变量
- #import "CBHybrid.h"
- @implementation CBHybrid
- static int borderRadius = 5;
- static int height = 37;
接下来我们把CBBezie内drawRect里的代码换个地方,通过一系列的改变之后:我们可以创建一个resizable的Image用来取代之前固定尺寸的Image,然后我们可以持有该静态对象并便于重用
- - (UIImage *)drawBackgroundImageHighlighted:(BOOL)highlighted {
- }
首先,我们需要知道我们resizable Image的宽,为了优化性能,我们应该在图片的中间保留一个像素的宽
- float width = 1 + (borderRadius * 2);
高的话在这个case里就不是非常重要了,因为这个按钮的高度对渐变层来说已经足够可见,设置为37pt的话也是出于和其余几个按钮的高度相同的原因。
搞定之后,我们需要一个bitmap context来进行绘制,搞起~
- UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 0.0);
- CGContextRef context = UIGraphicsGetCurrentContext();
- CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
UIGraphicsBeginImageContextWithOptions第二个参数为NO的话确保我们创建的Image context是透明的(带Alpha),最后的参数是scale factor(屏幕密度),如果是0的话就是当前设备的默认scale factor。
接下来的代码就和我们之前用Core Graphics来实现CBBezier的Demo里用到的非常相像了。我们用highlighted参数替换默认的self.highlighted属性,把用作更新界面的图像的信息值保存下来。
- UIBezierPath *roundedRectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(0, 0, width, height) cornerRadius: borderRadius];
- [roundedRectanglePath addClip];
- CGGradientRef background = highlighted? highlightedGradient : gradient;
- CGContextDrawLinearGradient(context, background, CGPointMake(140, 0), CGPointMake(140, height-1), 0);
我们唯一需要添加的步骤就是,用UIGraphicsEndImageContext来保存图像信息,并且放到UIImage对象中。
- UIImage* backgroundImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
现在我们已经完成了创建背景图片的方法,接下来我们必须实现一个通用的初始化方法用来实例化Images,并且把他们设置为CBHybird实例真正的背景。
- - (void)setupBackgrounds {
- if (!gBackgroundImage && !gBackgroundImageHighlighted) {
- gBackgroundImage = [[self drawBackgroundImageHighlighted:NO] resizableImageWithCapInsets:UIEdgeInsetsMake(borderRadius, borderRadius, borderRadius, borderRadius) resizingMode:UIImageResizingModeStretch];
- gBackgroundImageHighlighted = [[self drawBackgroundImageHighlighted:YES] resizableImageWithCapInsets:UIEdgeInsetsMake(borderRadius, borderRadius, borderRadius, borderRadius) resizingMode:UIImageResizingModeStretch];
- }
- [self setBackgroundImage:gBackgroundImage forState:UIControlStateNormal];
- [self setBackgroundImage:gBackgroundImageHighlighted forState:UIControlStateHighlighted];
- }
我们可以用custom的类型来实例化我们的CBHybird,当然也可以用initWithCoder,如果想用在代码里实现的话我们还可以用initWithFrame(···其实这里我是不想翻译的,原作者写得真是太详细了,第一次翻译技术文章,还是彻底点吧。。。)
- + (CBHybrid *)buttonWithType:(UIButtonType)type
- {
- return [super buttonWithType:UIButtonTypeCustom];
- }
- - (id)initWithCoder:(NSCoder *)aDecoder {
- self = [super initWithCoder:aDecoder];
- if (self) {
- [self setupBackgrounds];
- }
- return self;
- }
为了确保我们新建的BHybird类能正常使用,在Interface Builder里我们赋值一个button,把实现类改成CBHybird后,把button的content内容改为CGContext-generated image(便于区分)。是驴是马,咱们cmd+R跑起来试试。
【结束语】
所有都搞定之后,我们发现用预资源加载绘制仍然比任何一种基于代码实现的方案表现得更为出色。然而,Core Graphicsis在效率和灵活性上更有优势。因此,基于两种技巧的混合方案在现在看来包含了以上两个的长处,而且在性能上也并没有明显的影响。
CocoaChina是全球最大的苹果开发中文社区,官方微信每日定时推送各种精彩的研发教程资源和工具,介绍app推广营销经验,最新企业招聘和外包信息,以及Cocos2d引擎、Cocostudio开发工具包的最新动态及培训信息。关注微信可以第一时间了解最新产品和服务动态,微信在手,天下我有!
为iOS设计:图形和性能的更多相关文章
- 非官方的iOS设计指南
非官方的iOS设计指南 有时候为iOS设计app并不是一件简单的事,但是如果你能找到正确的最新的苹果设备信息,并按照正确的方向,那么为iOS设计app或许会变得简单容易些. 关于这些指南 这些指南描述 ...
- 【译】UI设计基础(UI Design Basics)--为iOS设计(Design for iOS)(二)
2.1 为iOS设计(Design for iOS) iOS体现以下主题: 遵从:UI帮助用户理解界面内容并与内容交互,但绝不会与内容相互冲突. 清晰:文本在任何尺寸下都是清晰易读,图标精确易懂,装饰 ...
- 学习ios设计(1)
两年前,苹果为现代的使用者完全改变了设计语言.对于设计者来说,这使得他们更容易关注动画和功能而不是其他的细枝末节. 我已经被问过很多次怎样开始设计或者是有什么捷径可以成为更好的设计师.虽然没有银弹,然 ...
- iOS设计指南
备忘:iOS设计指南:http://www.ui.cn/detail/32167.html
- ios设计一部WindowsPhone手机
ios设计一部WindowsPhone手机 main.m #import <Foundation/Foundation.h> #import "WindowsPhone.h&qu ...
- 学习iOS设计--iOS8的颜色、文字和布局学习
在去年,Apple针对新时代用户彻底更新了其设计语言.现在的设计语言相对之前大为简化,能够让设计师将精力集中到动画和功能上,而不是繁复的视觉细节上. 很多人都曾问过我:设计应当如何入门?成为一名优秀设 ...
- iOS实现图形编程可以使用三种API(UIKIT、Core Graphics、OpenGL ES及GLKit)
这些api包含的绘制操作都在一个图形环境中进行绘制.一个图形环境包含绘制参数和所有的绘制需要的设备特定信息,包括屏幕图形环境.offscreen 位图环境和PDF图形环境,用来在屏幕表面.一个位图或一 ...
- iOS设计 - 一款APP从设计稿到切图过程概述
这篇文章站在GUI设计师的角度概述了APP从项目启动到切片输出的过程,相当于工作流程的介绍.这里写的不是一种规范,只是一种工作方法,加上技术的更新是非常快的,大家在具体工作中,一定要灵活运用. 这里我 ...
- 【读书笔记】iOS网络-优化请求性能
一,度量网络性能 1,网络带宽 用于描述无线网络性能的最常见度量指标就是带宽.在数字无线通信中,网络带宽可以描述为两个端点之间的通信通道每秒钟可以传输的位数.现代无线网络所能提供的理论带宽是很高的.不 ...
随机推荐
- java动态代理和cglib
1.代理类可以分为两种. 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译.在程序运行前,代理类的.class文件就已经存在了. 动态代理:在程序运行时,运用反射机制动态创建而成. 2.JD ...
- jsjl_for_ubuntu12.04
1. VC++代码: #include <stdio.h> #include <windows.h> #include <wchar.h> void MoveMou ...
- 显示Unicode 字节的对应字符的小技巧
在一段smali代码里看到这样的代码 const-string v0, "\u7528\u6237\u9a8c\u8bc1\u8fc7\u671f\uff0c\u8bf7\u91cd\u65 ...
- LeetCode--122--卖卖股票的最佳时机II
问题描述: 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格. 设计一个算法来计算你所能获取的最大利润.你可以尽可能地完成更多的交易(多次买卖一支股票). 注意:你不能同时参与多笔交易( ...
- 20170609批量生成WORD合同
Sub NextSeven_CodeFrame() Application.ScreenUpdating = False Application.DisplayAlerts = False Appli ...
- codeforces 555b//Case of Fugitive// Codeforces Round #310(Div. 1)
题意:有n-1个缝隙,在上面搭桥,每个缝隙有个ll,rr值,ll<=长度<=rr的才能搭上去.求一种搭桥组合. 经典问题,应列入acm必背300题中.属于那种不可能自己想得出来的题.将二元 ...
- memcached set命令的大致处理逻辑笔记
这次记录状态机的主要逻辑,跟踪set命令的执行流程,暂不涉及到内存申请这一块,下面内容基本都是代码注释 首先还是补充了解下客户连接在发送数据到数据被处理并返回过程中conn的各种状态的表示 enum ...
- Android控件Gridview实现多个menu模块,可添加可删除
此案例主要讲的是Android控件Gridview(九宫格)完美实现仿支付宝首页,包含添加和删除功能:Fragment底部按钮切换的效果,包含四个模块,登录页面圆形头像等,一个小项目的初始布局. 效果 ...
- 为tomcat指定jdk
安装好jdk,不配置环境变量 windows平台 在catalina.bat文件和setclasspath.bat文件开头的空白处加上如下两句(指定JDK): \jre linux平台 在catali ...
- python-day20--collections模块
1.namedtuple: 生成可以使用名字来访问元素内容的tuple >>> from collections import namedtuple >>> Poi ...