![](https://img.haomeiwen.com/i2368622/e18aea1252a0a39f.jpg)
尽管IOS中的技术堆栈基本保持不变,但在本文中进一步描述的混合方法被当今的标准所取代。如果你想用Xcode和Swift来设计iOS接口,请查看Building iOS Interfaces系列。
在前一篇 文章中,我们探索不同的技术来定制UIButton的外观和感觉。基于实现中涉及的Objective-C 代码的复杂性为每个级别分配一个难度级别。然而,我故意不提的是,这些方法中有一些具有特别高的性能,在选择一个时,应该考虑到这些因素。
幕后
为了了解性能如何受到影响,我们需要仔细研究iOS中图形框架背后的技术堆栈。这个方框图代表了不同的frameworks和libraries,以及它们是如何相互关联的:
![](https://img.haomeiwen.com/i2368622/db9162f61f8bcdca.png)
在最上层,有一个高级的Objective-C框架UIKit,它管理IOS中的图形用户界面。它由一组类组成,每个类对应于一个特定的UI控件,例如UIButton和UILablel。UIKit本身是建立在核心动画之上的,它是在OS X Leopard中引入的一个框架,并移植到iOS上,为它后来所知道的平滑过渡提供了动力。
在UIKit框架底层一些,我们有OpenGL ES,一个开放的标准库,用于在移动设备上绘制2D和3D计算机图形。它广泛用于游戏图形和核心动画和UIKit。软件堆栈中的最后一块是Core Graphics,历史上被称为“ Quartz”-它是一个基于CPU的绘图引擎,它在OSX上首次亮相。这两个低级框架都是用C语言编写的。
图中的底部行代表硬件堆栈,由图形卡(GPU)和主处理器(CPU)组成。
我们平时所说的加速,GPU用于合成和渲染图形时,比如OpenGL的案例和Core Animation/UIKit实现。直到最近,硬件加速是IOS在Android上的一个主要优势;后者的大部分动画由于其依赖于绘图的CPU而倍受关注。
另一方面,屏幕绘制是指在使用CPU之前在后台生成位图图形的过程,然后将它们交给GPU进行屏幕渲染。在IOS中,屏幕绘图在下列任何情况下自动发生:
·核心图形(前缀CG *的任何类)
·
drawRect()
方法,即使是空的实现。·
CALayers
的属性shouldRasterize
设置为YES
。·
CALayers
使用函数(setMasksToBounds
)和设置阴影(setShadow*
)·在屏幕上显示的任何文本,包括Core Text.。
·设置组透明度(
UIViewGroupOpacity
)
一般来说,当绘制动画时会影响性能。您可以通过使用iOS设备的工具查看屏幕上的UI的哪些部分影响屏幕效果
1.用USB插入你的开发准备好的iOS设备
2.打开Xcode如图菜单使用Instruments工具
![](https://img.haomeiwen.com/i2368622/17ffbfd39890a36a.png)
3.选择Core Animation
![](https://img.haomeiwen.com/i2368622/1c623f27b5361b54.png)
![](https://img.haomeiwen.com/i2368622/cb630dc321b4bf40.png)
更新:还可以在ios模拟器中通过Debug的形式检测丽萍渲染。除非你正在做性能测试,这里使用模拟器是检查屏幕渲染最简单、最直接的方法。
![](https://img.haomeiwen.com/i2368622/0e8ed56cf983ec59.png)
检测UIButton情况
现在让我们看一下前面介绍的每种方法的性能方案。
预加载
创建我们的按钮使用UIImage
背景完全依赖于GPU来渲染磁盘上保存的图像。可调整的背景图像变体被认为是最小的资源方法,因为它导致较小的APP体积,并且在拉伸或平铺像素时利用硬件加速。
使用CALayers
我们实现的基于CALayer-based的方法绘制屏幕,因为它使用masking来渲染圆角。当使用内核动画时,我们还必须显式禁用默认打开的动画。底线,除非你需要动画过渡,这种技术是不够的自定义绘图。
使用drawRect
drawRect
方法依赖于Core Graphics
去绘制,但它的主要缺点在于它处理触摸事件的方式。每次button出发点击事件时setNeedsDisplay
会重新绘制,不止一次,但是每一次敲击两次。这不是CPU和内存的良好使用,特别是如果在接口中有多个UIButton实例。
Hybrid方法
那么,这是否意味着使用预渲染资源是唯一可行的解决方案?答案当然是否定的。如果您仍然需要使用代码绘图的灵活性,那么有一些技术可以优化代码并减少其性能占用。一种方法是生成可伸缩位图图像并在所有实例上重用它。
我们将首先创建一个新的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;
接下来,我们将我们的绘图代码从CBBezier
中的drawRect
移动到一个新的辅助方法。我们将生成一个可缩放的图像,而不是一个完整大小的图像,然后将输出保存到静态变量,以便以后重用:
- (UIImage *)drawBackgroundImageHighlighted:(BOOL)highlighted {
// Drawing code goes here
}
首先,我们需要得到可调整图像的宽度。为了获得最佳性能,我们希望在图像垂直中心的1PT可伸展区域。
float width = 1 + (borderRadius * 2);
在这种情况下,高度很小,只要按钮足够高,可以看到梯度。选择了37pt的值以匹配其他按钮的高度。
继续前进,我们需要一个位图上下文来绘制,所以让我们创建一个:
UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
将第二布尔参数设置为NO
将确保我们的图像上下文不是不透明的。最后一个参数是比例因子(屏幕密度)。当设置为0时,它默认设备的比例因子。
下一个模块将与我们以前在CBBezier的核心图形实现完全一样,保存更新的值,使用突出显示的highlighted
参数,而不是默认的self.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...
与CBBezier
相比,我们需要添加的唯一步骤是一种方法,该方法可以保存UIImage
中的输出,并调用UIGraphicsEndImageContext
来清除我们之后的内容。
UIImage* backgroundImage = UIGraphicsGetImageFromCurrentImageContext();
// Cleanup
UIGraphicsEndImageContext();
现在我们有了一个生成背景图像的方法,我们将需要实现一个公共初始化器方法,这些方法将实例化这些图像并将它们设置为CBHybrid
实例的背景。
- (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];
}
我们将通过将按钮类型设置为custom
和执行initWithCoder
(或者initWithFrame
如果在代码中创建按钮实例)
+ (CBHybrid *)buttonWithType:(UIButtonType)type
{
return [super buttonWithType:UIButtonTypeCustom];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self setupBackgrounds];
}
return self;
}
要确保新的子类工作正常,请复制接口生成器中的一个按钮,并将其类更改为CBHybrid
。将按钮内容更改为CGContext-generated
生成的图像,然后生成并运行。
![](https://img.haomeiwen.com/i2368622/abbee2b3576acae1.png)
完整的子类代码可以在这里找到。
结束语
无论别人说什么或者做什么,预渲染资源的方法仍然比任何基于代码实现的解决方案都要好。同样,一旦核心图形化了这一点,在灵活性和效率方面有很大的收获,而我们刚刚覆盖的混合方法不会影响到当今硬件上任何值得注意的程度。
更新:Andy Matuschak是UIKit团队的一员,很好地提供了关于屏幕外渲染的更多解释,以及对评论部分中的缓存清除的一些很好的见解。 provide more clarifications
本人能力一般水平有限,但是本着学习的态度希望能坚持的看一下国外大神的博客,来有所提高,写成博客也只不过是本着做笔记的态度而已,里面必然很多问题,希望大家指正
网友评论