最近做的项目主要是LBS这块 主打成员定位功能 我们的UI设计是这样的
分析
首先看下我是怎么实现这个annotationView的 由于这个annotationsView是异形的(也就是无法通过设置圆角直接得到) 而且里面的图片还因用户而异 所以解决方案就是使用layer.mask来进行遮罩 代码如下
@implementation MMAnnotationView
- (instancetype)initWithAnnotation:(id)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if ( self )
{
self.frame = CGRectMake(0, 0, TRACK_ANNOTATION_SIZE.width, TRACK_ANNOTATION_SIZE.height);
self.centerOffset = CGPointMake(0, -(TRACK_ANNOTATION_SIZE.height-3)/2);
self.canShowCallout = NO;
self.avatarView = [[UIImageView alloc] initWithFrame:self.bounds];
[self addSubview:self.avatarView];
self.avatarView.contentMode = UIViewContentModeScaleAspectFill;
CAShapeLayer *shapelayer = [CAShapeLayer layer];
shapelayer.frame = self.bounds;
shapelayer.path = self.framePath.CGPath;
yer.mask = shapelayer;
yer.shadowPath = self.framePath.CGPath;
yer.shadowRadius = 1.0f;
yer.shadowColor = [UIColor colorWithHex:0x666666FF].CGColor;
yer.shadowOpacity = 1.0f;
yer.shadowOffset = CGSizeMake(0, 0);
yer.masksToBounds = NO;
}
return self;
}
//mask路径
- (UIBezierPath *)framePath
{
if ( !_framePath )
{
CGFloat arrowWidth = 14;
CGMutablePathRef path = CGPathCreateMutable();
CGRect rectangle = CGRectInset(CGRectMake(0, 0, CGRectGetWidth(self.bounds), CGRectGetWidth(self.bounds)), 3,3);
CGPoint p[3] = {
{CGRectGetMidX(self.bounds)-arrowWidth/2, CGRectGetWidth(self.bounds)-6},
{CGRectGetMidX(self.bounds)+arrowWidth/2, CGRectGetWidth(self.bounds)-6},
{CGRectGetMidX(self.bounds), CGRectGetHeight(self.bounds)-4}
};
CGPathAddRoundedRect(path, NULL, rectangle, 5, 5);
CGPathAddLines(path, NULL, p, 3);
CGPathCloseSubpath(path);
_framePath = [UIBezierPath bezierPathWithCGPath:path];
CGPathRelease(path);
}
return _framePath;
}
我用代码生成了形状路径 并以此生成了layer的mask和shadowPath
使用时 只要直接用SDWebImage设置头像就行了
1
[annotationView.avatarView sd_setImageWithURL:[NSURL URLWithString:avatarURL] placeholderImage:placeHolderImage];
接下来用工具分析一下问题出来哪 分析性能当然是选择Instrments(用法在这里就不做介绍了) 打开Core Animation 然后运行程序 滑动地图 可以看到性能分析如下
再使用Debug Option来深入分析一下
Color Blended Layers
Color Misaligned Images
Color Offscreen-Rendered Yellow
分别打开这几个选项 结果如下
Color Blended Layers没有问题 不过这也是正常的 由于使用了mask 没有透明的地方
Color Misaligned Images除了默认头像外全中 这是因为服务器上的图片大小跟显示的大小不一致 导致缩放 而默认头像则是一致的 所以没问题
Color Offscreen-Rendered Yellow全中 由于使用了mask 导致大量的离屏渲染 这也是性能下降的主要原因
解决
问题的原因找到了 那么接下来该如何解决呢?
首先mask是肯定不能用了
其次下载下来的图片我们要预处理成实际大小
那么 直接把下载下来的图片合成为我们要显示的最终结果不就ok了吗? 试试看
- (void)loadAnnotationImageWithURL:(NSString*)url imageView:(UIImageView*)imageView
{
//将合成后的图片缓存起来
NSString *annoImageURL = url;
NSString *annoImageCacheURL = [annoImageURL stringByAppendingString:@"cache"];
UIImage *cacheImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:annoImageCacheURL];
if ( cacheImage )
{
//LLLog(@"hit cache");
imageView.image = cacheImage;
}
else
{
//LLLog(@"no cache");
[imageView sd_setImageWithURL:[NSURL URLWithString:annoImageURL]
placeholderImage:placeHolderImage
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (!error)
{
UIImage *annoImage = [image annotationImage];
imageView.image = annoImage;
[[SDImageCache sharedImageCache] storeImage:annoImage forKey:annoImageCacheURL];
}
}];
}
}
@implementation UIImage (LJC)
- (UIImage*) annotationImage
{
static UIView *snapshotView = nil;
static UIImageView *imageView = nil;
if ( !snapshotView )
{
snapshotView = [UIView new];
snapshotView.frame = CGRectMake(0, 0, TRACK_ANNOTATION_SIZE.width, TRACK_ANNOTATION_SIZE.height);
imageView = [UIImageView new];
[snapshotView addSubview:imageView];
imageView.clipsToBounds = YES;
imageView.frame = snapshotView.bounds;
imageView.contentMode = UIViewContentModeScaleAspectFill;
CGFloat arrowWidth = 14;
CGMutablePathRef path = CGPathCreateMutable();
CGRect rectangle = CGRectInset(CGRectMake(0, 0, CGRectGetWidth(imageView.bounds), CGRectGetWidth(imageView.bounds)), 3,3);
CGPoint p[3] = {
{CGRectGetMidX(imageView.bounds)-arrowWidth/2, CGRectGetWidth(imageView.bounds)-6},
{CGRectGetMidX(imageView.bounds)+arrowWidth/2, CGRectGetWidth(imageView.bounds)-6},
{CGRectGetMidX(imageView.bounds), CGRectGetHeight(imageView.bounds)-4}
};
CGPathAddRoundedRect(path, NULL, rectangle, 5, 5);
CGPathAddLines(path, NULL, p, 3);
CGPathCloseSubpath(path);
CAShapeLayer *shapelayer = [CAShapeLayer layer];
shapelayer.frame = imageView.bounds;
shapelayer.path = path;
yer.mask = shapelayer;
yer.shadowPath = path;
yer.shadowRadius = 1.0f;
yer.shadowColor = [UIColor colorWithHex:0x666666FF].CGColor;
yer.shadowOpacity = 1.0f;
yer.shadowOffset = CGSizeMake(0, 0);
CGPathRelease(path);
}
imageView.image = self;
UIGraphicsBeginImageContextWithOptions(TRACK_ANNOTATION_SIZE, NO, 0);
[yer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *copied = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return copied;
}
@end
然后使用的时候 只要简单的如下调用就OK了
[self loadAnnotationImageWithURL:avatarURL imageView:annotationView.avatarView];
看看修改之后的Instruments表现如何
Color Misaligned Images没问题了 因为头像已被缩放成了相同大小
Color Offscreen-Rendered Yellow没问题了 因为只是简单的显示了一张图片 而并没有需要离屏渲染的东西了
再来看下帧数情况
小结
不光是MKMapView 其实包括UITableView在内的很多地方都可以用文中所说的方法去优化 其核心点就是 合成+缓存 当然 由于合成还是会耗费一部分资源的 所以比较适合头像这种小的资源
关于图形性能优化 可以看下这篇好文(有对文中提到的Debug Option不太明白的 这里有详细的解释)