之前的文章里,我们探讨了基于多种不同技术来实现自定义的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的子类,然后如下定义一些静态变量

  1. #import "CBHybrid.h"
  2. @implementation CBHybrid
  3. static int borderRadius = 5;
  4. static int height = 37;

接下来我们把CBBezie内drawRect里的代码换个地方,通过一系列的改变之后:我们可以创建一个resizable的Image用来取代之前固定尺寸的Image,然后我们可以持有该静态对象并便于重用

  1. - (UIImage *)drawBackgroundImageHighlighted:(BOOL)highlighted {
  2. }

首先,我们需要知道我们resizable Image的宽,为了优化性能,我们应该在图片的中间保留一个像素的宽

  1. float width = 1 + (borderRadius * 2);

高的话在这个case里就不是非常重要了,因为这个按钮的高度对渐变层来说已经足够可见,设置为37pt的话也是出于和其余几个按钮的高度相同的原因。

搞定之后,我们需要一个bitmap context来进行绘制,搞起~

  1. UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 0.0);
  2. CGContextRef context = UIGraphicsGetCurrentContext();
  3. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

UIGraphicsBeginImageContextWithOptions第二个参数为NO的话确保我们创建的Image context是透明的(带Alpha),最后的参数是scale factor(屏幕密度),如果是0的话就是当前设备的默认scale factor。

接下来的代码就和我们之前用Core Graphics来实现CBBezier的Demo里用到的非常相像了。我们用highlighted参数替换默认的self.highlighted属性,把用作更新界面的图像的信息值保存下来。

  1. UIBezierPath *roundedRectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(0, 0, width, height) cornerRadius: borderRadius];
  2. [roundedRectanglePath addClip];
  3. CGGradientRef background = highlighted? highlightedGradient : gradient;
  4. CGContextDrawLinearGradient(context, background, CGPointMake(140, 0), CGPointMake(140, height-1), 0);

我们唯一需要添加的步骤就是,用UIGraphicsEndImageContext来保存图像信息,并且放到UIImage对象中。

  1. UIImage* backgroundImage = UIGraphicsGetImageFromCurrentImageContext();
  2. UIGraphicsEndImageContext();

现在我们已经完成了创建背景图片的方法,接下来我们必须实现一个通用的初始化方法用来实例化Images,并且把他们设置为CBHybird实例真正的背景。

  1. - (void)setupBackgrounds {
  2. if (!gBackgroundImage && !gBackgroundImageHighlighted) {
  3. gBackgroundImage = [[self drawBackgroundImageHighlighted:NO] resizableImageWithCapInsets:UIEdgeInsetsMake(borderRadius, borderRadius, borderRadius, borderRadius) resizingMode:UIImageResizingModeStretch];
  4. gBackgroundImageHighlighted = [[self drawBackgroundImageHighlighted:YES] resizableImageWithCapInsets:UIEdgeInsetsMake(borderRadius, borderRadius, borderRadius, borderRadius) resizingMode:UIImageResizingModeStretch];
  5. }
  6. [self setBackgroundImage:gBackgroundImage forState:UIControlStateNormal];
  7. [self setBackgroundImage:gBackgroundImageHighlighted forState:UIControlStateHighlighted];
  8. }

我们可以用custom的类型来实例化我们的CBHybird,当然也可以用initWithCoder,如果想用在代码里实现的话我们还可以用initWithFrame(···其实这里我是不想翻译的,原作者写得真是太详细了,第一次翻译技术文章,还是彻底点吧。。。)

  1. + (CBHybrid *)buttonWithType:(UIButtonType)type
  2. {
  3. return [super buttonWithType:UIButtonTypeCustom];
  4. }
  5. - (id)initWithCoder:(NSCoder *)aDecoder {
  6. self = [super initWithCoder:aDecoder];
  7. if (self) {
  8. [self setupBackgrounds];
  9. }
  10. return self;
  11. }

为了确保我们新建的BHybird类能正常使用,在Interface Builder里我们赋值一个button,把实现类改成CBHybird后,把button的content内容改为CGContext-generated image(便于区分)。是驴是马,咱们cmd+R跑起来试试。

 
完整的子类实现代码在这里~~~

【结束语】

所有都搞定之后,我们发现用预资源加载绘制仍然比任何一种基于代码实现的方案表现得更为出色。然而,Core Graphicsis在效率和灵活性上更有优势。因此,基于两种技巧的混合方案在现在看来包含了以上两个的长处,而且在性能上也并没有明显的影响。

CocoaChina是全球最大的苹果开发中文社区,官方微信每日定时推送各种精彩的研发教程资源和工具,介绍app推广营销经验,最新企业招聘和外包信息,以及Cocos2d引擎、Cocostudio开发工具包的最新动态及培训信息。关注微信可以第一时间了解最新产品和服务动态,微信在手,天下我有!

为iOS设计:图形和性能的更多相关文章

  1. 非官方的iOS设计指南

    非官方的iOS设计指南 有时候为iOS设计app并不是一件简单的事,但是如果你能找到正确的最新的苹果设备信息,并按照正确的方向,那么为iOS设计app或许会变得简单容易些. 关于这些指南 这些指南描述 ...

  2. 【译】UI设计基础(UI Design Basics)--为iOS设计(Design for iOS)(二)

    2.1 为iOS设计(Design for iOS) iOS体现以下主题: 遵从:UI帮助用户理解界面内容并与内容交互,但绝不会与内容相互冲突. 清晰:文本在任何尺寸下都是清晰易读,图标精确易懂,装饰 ...

  3. 学习ios设计(1)

    两年前,苹果为现代的使用者完全改变了设计语言.对于设计者来说,这使得他们更容易关注动画和功能而不是其他的细枝末节. 我已经被问过很多次怎样开始设计或者是有什么捷径可以成为更好的设计师.虽然没有银弹,然 ...

  4. iOS设计指南

    备忘:iOS设计指南:http://www.ui.cn/detail/32167.html

  5. ios设计一部WindowsPhone手机

    ios设计一部WindowsPhone手机 main.m #import <Foundation/Foundation.h> #import "WindowsPhone.h&qu ...

  6. 学习iOS设计--iOS8的颜色、文字和布局学习

    在去年,Apple针对新时代用户彻底更新了其设计语言.现在的设计语言相对之前大为简化,能够让设计师将精力集中到动画和功能上,而不是繁复的视觉细节上. 很多人都曾问过我:设计应当如何入门?成为一名优秀设 ...

  7. iOS实现图形编程可以使用三种API(UIKIT、Core Graphics、OpenGL ES及GLKit)

    这些api包含的绘制操作都在一个图形环境中进行绘制.一个图形环境包含绘制参数和所有的绘制需要的设备特定信息,包括屏幕图形环境.offscreen 位图环境和PDF图形环境,用来在屏幕表面.一个位图或一 ...

  8. iOS设计 - 一款APP从设计稿到切图过程概述

    这篇文章站在GUI设计师的角度概述了APP从项目启动到切片输出的过程,相当于工作流程的介绍.这里写的不是一种规范,只是一种工作方法,加上技术的更新是非常快的,大家在具体工作中,一定要灵活运用. 这里我 ...

  9. 【读书笔记】iOS网络-优化请求性能

    一,度量网络性能 1,网络带宽 用于描述无线网络性能的最常见度量指标就是带宽.在数字无线通信中,网络带宽可以描述为两个端点之间的通信通道每秒钟可以传输的位数.现代无线网络所能提供的理论带宽是很高的.不 ...

随机推荐

  1. Jmeter ResponseAssertion 【Ignore Status】

    在Jmeter源码中AssertionGui.java中,定义了Ignore Status的作用域 /** * Checkbox to indicate whether the response sh ...

  2. Java 面向对象之接口、多态

    01接口的概念 A:接口的概念 接口是功能的集合,同样可看做是一种数据类型,是比抽象类更为抽象的”类”. 接口只描述所应该具备的方法,并没有具体实现,具体的实现由接口的实现类(相当于接口的子类)来完成 ...

  3. 如何上传本地文件到github又如何删除自己的github仓库

    首先自己在https://github.com/网站要注册一个账户 自己上传工程到jithub,没有付费的用户只能选用public,意味着你的项目在全网是可以被看到和下载的: 所以涉及私密信息的,需要 ...

  4. 自定义 Maven 的 repositories

    有时,应用中需要一些比较新的依赖,而这些依赖并没有正式发布,还是处于milestone或者是snapshot阶段,并不能从中央仓库或者镜像站上下载到.此时,就需要 自定义Maven的repositor ...

  5. 多个 CancellationTokenSource 复合(组合) 或 C# 使用 CancellationTokenSource 终止线程

    https://www.cnblogs.com/luohengstudy/p/5623451.html https://www.cnblogs.com/wlzhang/p/4604471.html

  6. .NET中使用Rabbit MQ

    1.通过Nuget 获取Rabbit MQ NET client bindings from NuGet: PM> Install-Package RabbitMQ.Client 2.发送者(生 ...

  7. kubectl管理多个k8s集群

    #把每个k8s集群的json配置文件放到/root/.kube/目录下,改为不同名字,通过--kubeconfig实现不同集群操作 kubectl --kubeconfig=/root/.kube/m ...

  8. SqlDataReader的用法

    datareader对象提供只读单向数据的快速传递,单向:您只能依次读取下一条数据;只读:DataReader中的数据是只读的,不能修改;相对地,DataSet中的数据可以任意读取和修改 01.usi ...

  9. C++技能重拾2

    13.类成员函数重载:局部同名函数将隐藏而不是重载全局声明,不引入父类名字空间时子类的同名函数不会和父类的构成重载,静态成员函数可以和非静态成员函数构成重载.本质是重载函数的定义是在相同的声明域里!! ...

  10. jenkins安装笔记(一)

    转载地址:https://www.cnblogs.com/sylvia-liu/p/4485311.html 自动化架构搭建过程中为实现当开发源码更新时自动化脚本同步触发自动执行,使用到持续集成工具J ...