0x01 场景分析
当我们需要完全展示一张大图时(注意不是要做大图在小容器展示的优化)。就包含了这张大图在屏幕内可以自由滑动显示。
比如大家都用过的地图,在展示的时候是一小块一小块进行加载的
首先我们知道如果直接使用UIImageView
整个填充,那其尺寸与image
尺寸是一样的。图片大小会被整个加载到内存中,这样就会导致内存占用非常严重,那我们可以怎么处理呢?
iOS
提供了一个图层CATiledLayer
来实现大图的分块展示。
翻译: Tiled 平铺、 瓷砖、 平铺显示、 并列显示
进本的使用思路如下:
1. 使用代码或者让UI
设计师将大图切成小图
2. 使用CATiledLayer
进行要展示的区域图片绘制
0x02 使用代码切小图
以下为可执行文件工具代码,在Mac OS
上运行
// main.m
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
//从入参中读取大图路径
NSString *inputFile = [NSString stringWithCString:argv[1] encoding:NSUTF8StringEncoding];
// 设置图片最大宽高为 256 像素
CGFloat tileSize = 256;
// 指定输出路径,与入参图片路径同级
NSString *outputPath = [inputFile stringByDeletingPathExtension];
//加载图片
NSImage *image = [[NSImage alloc] initWithContentsOfFile:inputFile];
NSSize size = [image size];
NSArray *representations = [image representations];
if ([representations count]){
NSBitmapImageRep *representation = representations[0];
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);
for (int y = 0; y < rows; ++y) {
for (int x = 0; x < cols; ++x) {
//extract tile image
CGRect tileRect = CGRectMake(x*tileSize, y*tileSize, tileSize, tileSize);
CGImageRef tileImage = CGImageCreateWithImageInRect(imageRef, tileRect);
// 转换为jpg图片
NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:tileImage];
NSData *data = [imageRep representationUsingType: NSJPEGFileType properties:nil];
CGImageRelease(tileImage);
// 保存文件至输出目录
NSString *path = [outputPath stringByAppendingFormat: @"_%02i_%02i.jpg", x, y];
[data writeToFile:path atomically:NO];
}
}
}
return 0;
}
0x03 使用CATiledLayer
展示
以下为iOS
项目代码
@interface ViewController ()<CALayerDelegate>
// 构建一个容器来展示大图,保证其contenSize与图片尺寸大小一致
@property (nonatomic, weak) IBOutlet UIScrollView *scrollView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 创建图层,并设置属性信息
CATiledLayer *tileLayer = [CATiledLayer layer];
CGFloat scale = UIScreen.mainScreen.scale; // 确保scale比例一致
tileLayer.frame = CGRectMake(0, 0, 3972/scale,15718/scale);// 图片像素
tileLayer.delegate = self;
tileLayer.tileSize = CGSizeMake(256/scale, 256/scale); // 每个瓷砖块的大小
tileLayer.delegate = self;
[self.scrollView.layer addSublayer:tileLayer];
self.scrollView.contentSize = tileLayer.frame.size;
// 刷新当前屏幕Rect
[tileLayer setNeedsDisplay];
}
// 当滑动到不同区域时会调用此方法
- (void)drawLayer:(CATiledLayer *)layer inContext:(CGContextRef)ctx
{
// 确定坐标信息
CGRect bounds = CGContextGetClipBoundingBox(ctx);
NSInteger x = floor(bounds.origin.x / layer.tileSize.width);
NSInteger y = floor(bounds.origin.y / layer.tileSize.height);
// 加载小图
NSString *imageName = [NSString stringWithFormat: @"zz_%02i_%02i", x, y];
NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:@"jpg"];
UIImage *tileImage = [UIImage imageWithContentsOfFile:imagePath];
// 在TiledLayer上绘制图片
UIGraphicsPushContext(ctx);
[tileImage drawInRect:bounds];
UIGraphicsPopContext();
}
0x03 其他属性
产生模糊的根源是图层的细节层次(level of detail
,简称LOD
)
-
levelsOfDetail
:
缩小视图是,最大可以达到的缩小级数。指图层维护的
LOD
数目,默认值为1
,每进一级会对前一级分辨率的一半进行缓存,图层的levelsOfDetail
最大值,也就是最底层细节,对应至少一个像素点。 -
levelsOfDetailBias
:
从最小视图需要放大多少次,才能达到我们需要的清晰度效果,注意是多少次,一次就是2倍指的是该图层缓存的放大
LOD
数目,默认为0
,即不会额外缓存放大层次,每进一级会对前一级两倍分辨率进行缓存。 tileSize
: 用于创建层内容的每个平铺的最大大小,默认为256*256
。
需要注意下,如果tileSize
设置太小就会把每块瓷砖图片展示的很小
0x04 总结
大图展示,越是尺寸比较大,像素比较高,在性能优化时得到的效果就越明显。
本文并无深度仅作为随笔记录这块功能的基本使用,便于后续强化记忆。