为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的子类,然后如下定义一些静态变量

  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开发工具包的最新动态及培训信息。关注微信可以第一时间了解最新产品和服务动态,微信在手,天下我有!

时间: 2024-07-29 14:48:40

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

非官方的iOS设计指南

有时候为iOS设计app并不是一件简单的事,但是如果你能找到正确的最新的苹果设备信息,并按照正确的方向,那么为iOS设计app或许会变得简单容易些. 关于这些指南 这些指南描述了如何遵守苹果的iOS 人机交互指南来设计app,而不是讲用自定义控件可以做成什么样的设计,有时候打破规则也很重要.该文档的目的并不是为一些复杂的设计问题提供解决方案.该文档是非官方的,将会定期更新和扩充内容,最近一次更新是2014年11月11日. 分辨率和显示屏规格(Resolutions和Display Specifi

iOS 设计指南

有时候为iOS设计app并不是一件简单的事,但是如果你能找到正确的最新的苹果设备信息,并按照正确的方向,那么为iOS设计app或许会变得简单容易些. 关于这些指南 这些指南描述了如何遵守苹果的iOS 人机交互指南来设计app,而不是讲用自定义控件可以做成什么样的设计,有时候打破规则也很重要.该文档的目的并不是为一些复杂的设计问题提供解决方案.该文档是非官方的,将会定期更新和扩充内容,最近一次更新是2014年11月11日. 分辨率和显示屏规格(Resolutions和Display Specifi

iOS 设计中关于UIScrollViewDelegate的几个代理方法的简单介绍

在ios设计的过程中,对于UIScrollView这个控件对于开发者而言都不会陌生,在处理UI界面的时候我们经常会用到UIScrollView,既然用到了UIScrollView,那么UIScrollView的几个代理方法就无法避免的被使用了.本文并不介绍UIScrollView的相关属性,就介绍几个代理方法. / 此方法在scrollView滑动时会被调用多次,只要scrollView.contentOffset发生改变就会被调用 / (void)scrollViewDidScroll:(UI

ios 基本图形的绘制 基于bitmap 位图

内容包括 图片水印,图片裁剪,屏幕截图,背景平铺 1.图片水印功能 #import "UIImage+MJ.h" @implementation UIImage (MJ) + (instancetype)waterImageWithBg:(NSString *)bg logo:(NSString *)logo { UIImage *bgImage = [UIImage imageNamed:bg]; // 1.创建一个基于位图的上下文(开启一个基于位图的上下文) UIGraphicsB

ios 基本图形的绘制

基本图形的绘制 包括: 代码画线,画文字 图片 裁剪 重绘  简单动画 当自定义view的时候 系统会自动调用drawRect 方法 画线 - (void)drawRect:(CGRect)rect { // Drawing code // 1.获得图形上下文 CGContextRef ctx = UIGraphicsGetCurrentContext(); // 2.拼接图形(路径) // 设置线段宽度 CGContextSetLineWidth(ctx, 10); // 设置线段头尾部的样式

如何在响应式基础上提升移动性能///响应式不是万能的!教你提升响应式设计的移动性能(一)

如何在响应式基础上提升移动性能 摘要:如何在响应式基础上提升移动性能,从细节做起,结合网站,做好响应式页面的设计优化工作.,随着互联网的高速发展,合肥网站建设小编今天为大家介绍,为解决移动性能的响应式页面设计并不是万能的,而应该不断的改进,从而更好的为用户服务.随着互联网的高速发展,例如近段时间炒得火热的谷歌申请的无人机技术,以及电商门户网站阿里巴巴的上市等等,这都促进了互联网日新月异的变化. 所以作为网站seo人员不应固步自封,而应该努力的跟上时代的步伐,不仅需要学习相关的seo知识,而且对于

IOS设计指南

以前整理过一份关于关于<IOS应用提交时所需要的ICON>,之后IPhone 6/Plus出来,又多了二种尺寸,近期看到国外的一家网站上整理出比较全的尺寸,以及在IOS设计上的一些参考建议. 原文链接: <The iOS Design Guidelines>   分辨率和显示规格   应用的图标规格 文章后面还有:导航栏.搜索栏.状态栏.工具栏等一系列UI的示例说明,鼠标移入到图片上会显示具体的尺寸信息       

【原】iOS 设计中 图片后期简单处理的完美组合

iOS 设计中 图片后期简单处理的完美组合 四张图+.DS_Store (3张alpha通道“是”,1张没有alpha通道) 5,909,971 字节(磁盘上的 5.9 MB),共 5 项 第一步:转非alpha通道,工具 Alpha-Channel-Remover https://github.com/bpolat/Alpha-Channel-Remover  这个地址就哦了 http://alphachannelremover.blogspot.com 这个墙内墙外都试过没打开 转完后:变小

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

这些api包含的绘制操作都在一个图形环境中进行绘制.一个图形环境包含绘制参数和所有的绘制需要的设备特定信息,包括屏幕图形环境.offscreen 位图环境和PDF图形环境,用来在屏幕表面.一个位图或一个pdf文件中进行图形和图像绘制.在屏幕图形环境中进行的绘制限定于在一个UIView类或其子类的实例中绘制,并直接在屏幕显示,在offscreen位图或PDF图形环境中进行的绘制不直接在屏幕上显示. 一.UIKIT API UIKIT是一组Objective-C API,为线条图形.Quartz图像