先贴一个案例,我们做类似的图片展示的时候,都会需要对相应的图片进行裁剪以适应我们现实的大小,不然图片就会被拉伸或者压缩。
不知大家有类似需求时会怎么处理,之前我一直是获取到图片数据,然后对图片裁剪到与视图框相应的比例,然后在展示。贴上代码:
UIImage * image = (UIImage*)info[UIImagePickerControllerOriginalImage];
CGFloat scale = MIN(image.size.height / 124.0f, image.size.width / 124.0f);
UIImage *cropImage = [image cropToSize:CGSizeMake(124 * scale, 124 * scale) usingMode:NYXCropModeCenter];
我们需要显示图片时,第一时间会考虑到用 UIImageView
这个类来实现,然而,在iOS中,UIView
这个类本身就自带类似功能的实现,相比上一种方法感觉更简单快捷。
contents属性
CALayer
有一个属性叫做contents
,这个属性的类型被定义为id
,意味着它可以是任何类型的对象。在这种情况下,你可以给contents
属性赋任何值,你的app仍然能够编译通过。但是,在实践中,如果你给contents
赋的不是CGImage
,那么你得到的图层将是空白的。
contents
这个奇怪的表现是由Mac OS的历史原因造成的。它之所以被定义为id
类型,是因为在Mac OS系统上,这个属性对CGImage
和NSImage
类型的值都起作用。如果你试图在iOS平台上将UIImage
的值赋给它,只能得到一个空白的图层。一些初识Core Animation
的iOS开发者可能会对这个感到困惑。
头疼的不仅仅是我们刚才提到的这个问题。事实上,你真正要赋值的类型应该是CGImageRef
,它是一个指向CGImage
结构的指针。UIImage
有一个CGImage
属性,它返回一个CGImageRef
,如果你想把这个值直接赋值给CALayer
的contents
,那你将会得到一个编译错误。因为CGImageRef
并不是一个真正的Cocoa
对象,而是一个Core Foundation
类型。
尽管Core Foundation
类型跟Cocoa
对象在运行时貌似很像(被称作toll-free bridging),他们并不是类型兼容的,不过你可以通过bridged
关键字转换。如果要给图层的寄宿图赋值,你可以按照以下这个方法
layer.contents = (__bridge id)image.CGImage;
maskToBounds
默认情况下,UIView
仍然会绘制超过边界的内容或是子视图,在CALayer
下也是这样的。
UIView
有一个叫做clipsToBounds
的属性可以用来决定是否显示超出边界的内容,CALayer
对应的属性叫做masksToBounds
。
contentsRect
CALayer
的contentsRect
属性允许我们在图层边框里显示寄宿图的一个子域。这涉及到图片是如何显示和拉伸的,所以要比contentsGravity灵活多了
和bounds
,frame
不同,contentsRect
不是按点来计算的,它使用了单位坐标,单位坐标指定在0到1之间,是一个相对值(像素和点就是绝对值)。所以他们是相对与寄宿图的尺寸的。
结论
利用以上三个layer
的属性,我们就可以快速实现对示图片裁剪,贴上代码:
int width = pic.bmiddle.width;
int height = pic.bmiddle.height;
CGFloat scale = (height / width) / (imageView.height / imageView.width);
if (scale < 0.99 || isnan(scale)) {
// 宽图把左右两边裁掉
imageView.contentMode = UIViewContentModeScaleAspectFill;
imageView.layer.contentsRect = CGRectMake(0, 0, 1, 1);
} else {
// 高图只保留顶部
imageView.contentMode = UIViewContentModeScaleToFill;
imageView.layer.contentsRect = CGRectMake(0, 0, 1, (float)width / height);
}
这样,我们就可以根据图片的宽高比例,进行适当裁剪,达到我们所需的显示效果。