CATiledLayer
CATiledLayer
有些时候你可能需要绘制一个很大的图片,常见的例子就是一个高像素的照片或者是地球表面的详细地图。iOS应用通畅运行在内存受限的设备上,所以读取整个图片到内存中是不明智的。载入大图可能会相当地慢,那些对你看上去比较方便的做法(在主线程调用UIImage的-imageNamed:方法或者-imageWithContentsOfFile:方法)将会阻塞你的用户界面,至少会引起动画卡顿现象。
能高效绘制在iOS上的图片也有一个大小限制。所有显示在屏幕上的图片最终都会被转化为OpenGL纹理,同时OpenGL有一个最大的纹理尺寸(通常是20482048,或40964096,这个取决于设备型号)。如果你想在单个纹理中显示一个比这大的图,即便图片已经存在于内存中了,你仍然会遇到很大的性能问题,因为Core Animation强制用CPU处理图片而不是更快的GPU(见第12章『速度的曲调』,和第13章『高效绘图』,它更加详细地解释了软件绘制和硬件绘制)。
CATiledLayer为载入大图造成的性能问题提供了一个解决方案:将大图分解成小片然后将他们单独按需载入。让我们用实验来证明一下。
小片裁剪
这个示例中,我们将会从一个2048*2048分辨率的雪人图片入手。为了能够从CATiledLayer中获益,我们需要把这个图片裁切成许多小一些的图片。你可以通过代码来完成这件事情,但是如果你在运行时读入整个图片并裁切,那CATiledLayer这些所有的性能优点就损失殆尽了。理想情况下来说,最好能够逐个步骤来实现。
清单6.11 演示了一个简单的Mac OS命令行程序,它用CATiledLayer将一个图片裁剪成小图并存储到不同的文件中。
清单6.11 裁剪图片成小图的终端程序
#import int main(int argc, const char * argv[])
{
@autoreleasepool{
//handle incorrect arguments
if (argc < ) {
NSLog(@"TileCutter arguments: inputfile");
return ;
} //input file
NSString *inputFile = [NSString stringWithCString:argv[] encoding:NSUTF8StringEncoding]; //tile size
CGFloat tileSize = ; //output path
NSString *outputPath = [inputFile stringByDeletingPathExtension]; //load image
NSImage *image = [[NSImage alloc] initWithContentsOfFile:inputFile];
NSSize size = [image size];
NSArray *representations = [image representations];
if ([representations count]){
NSBitmapImageRep *representation = representations[];
size.width = [representation pixelsWide];
size.height = [representation pixelsHigh];
}
NSRect rect = NSMakeRect(0.0, 0.0, size.width, size.height);
CGImageRef imageRef = [image CGImageForProposedRect:&rect context:NULL hints:nil]; //calculate rows and columns
NSInteger rows = ceil(size.height / tileSize);
NSInteger cols = ceil(size.width / tileSize); //generate tiles
for (int y = ; y < rows; ++y) {
for (int x = ; x < cols; ++x) {
//extract tile image
CGRect tileRect = CGRectMake(x*tileSize, y*tileSize, tileSize, tileSize);
CGImageRef tileImage = CGImageCreateWithImageInRect(imageRef, tileRect); //convert to jpeg data
NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:tileImage];
NSData *data = [imageRep representationUsingType: NSJPEGFileType properties:nil];
CGImageRelease(tileImage); //save file
NSString *path = [outputPath stringByAppendingFormat: @"_%02i_%02i.jpg", x, y];
[data writeToFile:path atomically:NO];
}
}
}
return ;
}
这个程序将20482048分辨率的雪人图案裁剪成了64个不同的256256的小图。(256*256是CATiledLayer的默认小图大小,默认大小可以通过tileSize属性更改)。程序接受一个图片路径作为命令行的第一个参数。我们可以在编译的scheme将路径参数硬编码然后就可以在Xcode中运行了,但是以后作用在另一个图片上就不方便了。所以,我们编译了这个程序并把它保存到敏感的地方,然后从终端调用,如下面所示:
> path/to/TileCutterApp path/to/Snowman.jpg
The app is very basic, but could easily be extended to support additional arguments such as tile size, or to export images in formats other than JPEG. The result of running it is a sequence of 64 new images, named as follows:
这个程序相当基础,但是能够轻易地扩展支持额外的参数比如小图大小,或者导出格式等等。运行结果是64个新图的序列,如下面命名:
Snowman_00_00.jpg
Snowman_00_01.jpg
Snowman_00_02.jpg
...
Snowman_07_07.jpg
既然我们有了裁切后的小图,我们就要让iOS程序用到他们。CATiledLayer很好地和UIScrollView集成在一起。除了设置图层和滑动视图边界以适配整个图片大小,我们真正要做的就是实现-drawLayer:inContext:方法,当需要载入新的小图时,CATiledLayer就会调用到这个方法。
清单6.12演示了代码。图6.12是代码运行结果。
清单6.12 一个简单的滚动CATiledLayer实现
#import "ViewController.h"
#import @interface ViewController () @property (nonatomic, weak) IBOutlet UIScrollView *scrollView; @end @implementation ViewController - (void)viewDidLoad
{
[super viewDidLoad];
//add the tiled layer
CATiledLayer *tileLayer = [CATiledLayer layer];
tileLayer.frame = CGRectMake(, , , );
tileLayer.delegate = self; [self.scrollView.layer addSublayer:tileLayer]; //configure the scroll view
self.scrollView.contentSize = tileLayer.frame.size; //draw layer
[tileLayer setNeedsDisplay];
} - (void)drawLayer:(CATiledLayer *)layer inContext:(CGContextRef)ctx
{
//determine tile coordinate
CGRect bounds = CGContextGetClipBoundingBox(ctx);
NSInteger x = floor(bounds.origin.x / layer.tileSize.width);
NSInteger y = floor(bounds.origin.y / layer.tileSize.height); //load tile image
NSString *imageName = [NSString stringWithFormat: @"Snowman_%02i_%02i", x, y];
NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:@"jpg"];
UIImage *tileImage = [UIImage imageWithContentsOfFile:imagePath]; //draw tile
UIGraphicsPushContext(ctx);
[tileImage drawInRect:bounds];
UIGraphicsPopContext();
}
@end

图6.12 用UIScrollView滚动CATiledLayer
当你滑动这个图片,你会发现当CATiledLayer载入小图的时候,他们会淡入到界面中。这是CATiledLayer的默认行为。(你可能已经在iOS 6之前的苹果地图程序中见过这个效果)你可以用fadeDuration属性改变淡入时长或直接禁用掉。CATiledLayer(不同于大部分的UIKit和Core Animation方法)支持多线程绘制,-drawLayer:inContext:方法可以在多个线程中同时地并发调用,所以请小心谨慎地确保你在这个方法中实现的绘制代码是线程安全的。
Retina小图
你也许已经注意到了这些小图并不是以Retina的分辨率显示的。为了以屏幕的原生分辨率来渲染CATiledLayer,我们需要设置图层的contentsScale来匹配UIScreen的scale属性:
tileLayer.contentsScale = [UIScreen mainScreen].scale;
有趣的是,tileSize是以像素为单位,而不是点,所以增大了contentsScale就自动有了默认的小图尺寸(现在它是128128的点而不是256256).所以,我们不需要手工更新小图的尺寸或是在Retina分辨率下指定一个不同的小图。我们需要做的是适应小图渲染代码以对应安排scale的变化,然而:
//determine tile coordinate
CGRect bounds = CGContextGetClipBoundingBox(ctx);
CGFloat scale = [UIScreen mainScreen].scale;
NSInteger x = floor(bounds.origin.x / layer.tileSize.width * scale);
NSInteger y = floor(bounds.origin.y / layer.tileSize.height * scale);
通过这个方法纠正scale也意味着我们的雪人图将以一半的大小渲染在Retina设备上(总尺寸是10241024,而不是20482048)。这个通常都不会影响到用CATiledLayer正常显示的图片类型(比如照片和地图,他们在设计上就是要支持放大缩小,能够在不同的缩放条件下显示),但是也需要在心里明白。
CATiledLayer的更多相关文章
- CATiledLayer显示超大图片的解决方案
先对图片进行了裁剪 -> 很多小图片, 然后再根据显示 进行绘制 - (void)viewDidLoad { [super viewDidLoad]; [self cutImageAndSave ...
- iOS之CATiledLayer的属性简介和使用
1.CATiledLayer简介 CATiledLayer用于大型图片进行分割显示,需要显示的图片才会加载,直接上代码: - (void)viewDidLoad { [super viewDidLoa ...
- ios项目里扒出来的json文件
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px Menlo; color: #000000 } p.p2 { margin: 0.0px 0. ...
- Github上关于iOS的各种开源项目集合(强烈建议大家收藏,查看,总有一款你需要)
下拉刷新 EGOTableViewPullRefresh - 最早的下拉刷新控件. SVPullToRefresh - 下拉刷新控件. MJRefresh - 仅需一行代码就可以为UITableVie ...
- iOS - 开发类库
开发类库 UI 项目名称 项目信息 1.MJRefresh 仅需一行代码就可以为UITableView或者CollectionView加上下拉刷新或者上拉刷新功能.可以自定义上下拉刷新的文字说明. ...
- iOS及Mac开源项目和学习资料【超级全面】
UI 下拉刷新 EGOTableViewPullRefresh – 最早的下拉刷新控件. SVPullToRefresh – 下拉刷新控件. MJRefresh – 仅需一行代码就可以为UITable ...
- Core Animation编程指南
本文是<Core Animation Programming Guide>2013-01-28更新版本的译文.本文略去了原文中关于OS X平台上Core Animation相关内容.因为原 ...
- 【IOS笔记】Views
Views Because view objects are the main way your application interacts with the user, they have many ...
- iOS:iOS开发非常全的三方库、插件等等
iOS开发非常全的三方库.插件等等 github排名:https://github.com/trending, github搜索:https://github.com/search. 此文章转自git ...
随机推荐
- 如何查看当前项目Laya的引擎版本
打开项目后在调试控制台输入 Laya.version
- ASP.NET MVC 使用Redis共享Session
储存模式 1.InProc模式 这是ASP.NET默认的Session管理模式,在应用进程内维护Session. 2.StateServer模式 这是在服务器装了.NET环境后自带的一个StateSe ...
- 通过IFeatureClass 接口查询 IWorkspace, 查询通配符
IWorkspace pWsI = ((IDataset)pFtCls).Workspace 查询通配符 ISQLSyntax psqls = (ISQLSyntax)(((IDataset)pFtC ...
- ELK之生产日志收集构架(filebeat-logstash-redis-logstash-elasticsearch-kibana)
本次构架图如下 说明: 1,前端服务器只启动轻量级日志收集工具filebeat(不需要JDK环境) 2,收集的日志不进过处理直接发送到redis消息队列 3,redis消息队列只是暂时存储日志数据,不 ...
- First normal formal Second normal form
https://en.wikipedia.org/wiki/First_normal_form https://en.wikipedia.org/wiki/Second_normal_form A r ...
- GUID Partition Table (GPT)
https://en.wikipedia.org/wiki/GUID_Partition_Table https://zh.wikipedia.org/wiki/全局唯一标识分区表 GUID Part ...
- http://www.cnblogs.com/linxiyue/p/8244724.html
http://www.cnblogs.com/linxiyue/p/8244724.html
- Spark-自定义排序
一.自定义排序规则-封装类 import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} /** ...
- django源码笔记-【1】(转)
add by zhj:第二段代码有修改 原文:http://www.cnblogs.com/gaott/archive/2012/02/28/2371181.html 前言 Django是一个开放源代 ...
- 理解CopyOnWriteArrayList
CopyOnWriteArrayList,顾名思义,Write的时候总是要Copy,也就是说对于任何可变的操作(add.set.remove)都是伴随复制这个动作的 A thread-safe var ...