注:本文只是提取了其中的主要部分进行了翻译,文章出现在较早,现在可能有不同的地方,具体操作请依据新版本的Xcode进行使用,原文还有其他的参考,还是建议大家多看原文 PS:本文第一次翻译,英语较渣,如有翻译不合适的地方请大家留言指出。
显示的内幕
IOS图形显示的层次结构
UIKit是负责IOS的图层管理的接口。他有很多类组成,各个类能够响应具体的UIControl的事件,例如UIButton和UILable。UIKit是CA的上层,CA是用于处理平滑转换的框架。
OpenGL ES是开源的用于处理图形的框架,包括游戏动画和UIKit和CA,CG就是我们常说的Quartz,基于CPU的。
硬件加速就是我们常说的GPU渲染图形,例如用到OPenGL和基于它的CA和UIkit。大多数的动画要想显示的很好都应当使用GPU进行加速。
离屏渲染指的在GPU进行屏幕渲染前是用CPU来处理bitmap图形。以下几种情况会自动进行离屏渲染:
- CoreGraphic类(以CG开头的类)
- drawRect方法(即使是空实现即在你的代码中显示的声明了)
- CALayer的shouldRasterize设为YES
- CALayer使用setMaskToBounds或设置阴影setShadow*
- 任何字体的显示,即使是CoreText
- 透明组 UIGroupOpacity
我们可以使用Instruments来进行查看离屏渲染的部分
1.把设备连入MAC
2.Xcode中打开Instruments
3.选择IOS>Graphics>CA
4.打开详细面板
5.选择要查看的设备
6.勾选Color OffScreen-Render Yellow 按钮
7.打开设备即可看到离屏渲染的部分,用黄色标识
补充:因为MAC的CPU远高于IOS设备的CPU(这是补充部分。。。),如果要是进行性能测试的话最好用真机显示,如果只是想看本例中的形式,IOS模拟器也可以
用UIButton来举例
使用预渲染
对于保存在磁盘上的图像,当我们用一个UIImage时它默认是GPU进行渲染的,这种事低消耗的而且是利用GPU来伸缩或铺像素进行显示。
CALyer
当我们设置圆角矩形的时候会进行离屏渲染,此时不得不禁用动画。总的来说,如果需要动画,那么它是不可行的
DrawRect
它依赖于CG自定义动画,缺点在于事件处理的时候,一个点击会进行两次的setNeedsDisaplay的调用,增加CPU和内存的负担,特别是多个Button的时候
混合的方法(本文支持和建议的)
如果我们需要灵活的用代码来进行绘画,那么可行的方案是制造一个可伸缩的可重用的bitmap图像给所有的实例
首先常见一个子类集成UIButton,同时加入静态变量
// In CBHybrid.m
#import "CBHybrid.h"
@implementation CBHybrid
// Resizable background image for normal state
static UIImage *gBackgroundImage;
// Resizable background image for highlighted state
static UIImage *gBackgroundImageHighlighted;
// Background image border radius and height
static int borderRadius = 5;
static int height = 37;
</pre></div><div style="text-align:left"><pre name="code" class="objc">- (UIImage *)drawBackgroundImageHighlighted:(BOOL)highlighted {
// Drawing code goes here
}
设置图像的宽度(最佳操作设置1pt的可伸缩区域+2*radius,button默认进行背景图像拉伸,设置少了可以体验性能)和高度37pt,接下来bitmap Context
UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
参数NO指的是不透明,0.0指的是缩放比例和设备相同(不同的IOS的缩放比例是不同的视网膜屏的要高于非视网膜屏的,具体比例数据请自行度娘),接下来是用Bezier曲线进行绘图,要考虑到Highlighted的情况
// Gradient Declarations
// NSArray *gradientColors = ...
// Draw rounded rectangle bezier path
UIBezierPath *roundedRectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(0, 0, width, height) cornerRadius: borderRadius];
// Use the bezier as a clipping path
[roundedRectanglePath addClip];
// Use one of the two gradients depending on the state of the button
CGGradientRef background = highlighted? highlightedGradient : gradient;
// Draw gradient within the path
CGContextDrawLinearGradient(context, background, CGPointMake(140, 0), CGPointMake(140, height-1), 0);
// Draw border
// [borderColor setStroke...
// Draw Inner Glow
// UIBezierPath *innerGlowRect...
输出图像和进行清理工作
现在就有了实现背景图片的方法,下面是进行封装形成一个通用的初始化方法
- (void)setupBackgrounds {
// Generate background images if necessary
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];
}
// Set background for the button instance
[self setBackgroundImage:gBackgroundImage forState:UIControlStateNormal];
[self setBackgroundImage:gBackgroundImageHighlighted forState:UIControlStateHighlighted];
}
返回button类型和如果是用代码创建的button需要实现initWithCoder(或initWithFrame)
测试和运行:将butoon改成本文的子类并将内容改成CGContext-Gradient图像
代码:https://github.com/kaishin/custom-UIButton/blob/master/Custom%20UIButtons/CBHybrid.m
原文链接:https://robots.thoughtbot.com/designing-for-ios-graphics-performance