一行代码实现 UIView 镂空效果

这是一种实现 UIView 镂空效果的方案,能够快速实现任意形状的镂空、文字的镂空、带镂空的毛玻璃效果等。本质上是 UIViewmaskView 效果的「取反」。git

前言

首先来复习一下遮罩效果的实现。若是咱们有一张图片,又刚好有一个圆,当咱们把圆设置为图片的遮罩时,会获得这样的结果。github

代码实现看上去像是这样:编程

view.maskView = maskView;
复制代码

那么问题来了,若是咱们但愿获得下面的结果,该怎么作呢?这看起来像是图层的相减,即原来的图层减去遮罩的部分。markdown

惋惜苹果爸爸不够贴心,没有提供方便的接口调用。让咱们来看看能够怎么实现。ide

1、思路

咱们的最终目标是,封装出一个接口,调用方式相似于 maskView 属性,能够很方便地对一个 UIView 作镂空效果。oop

注: 如下用 originView 指代须要上效果的 view,用 maskView 指代充当遮罩的 viewui

目前看来,能够从两个方向入手:spa

  1. 修改遮罩的绘制过程
  2. 修改 maskView 自己

方式一是指,在设置这个属性的时候,对 originView 的视图进行从新绘制,而后在绘制的时候,减掉 maskView 的区域。3d

方式二是指,当拿到 maskView 的时候,先对 maskView 自己先进行处理,将遮罩范围取反。而后再作遮罩效果,因为遮罩的区域已经相反,因而获得的结果也是相反的,就达到镂空的目的。code

看上去方式二比较靠谱,并且最后是调用 UIViewsetMaskView: 来实现,还能够保留原来遮罩的一些特性。好比当修改 maskViewframe 的时候, originView 的遮罩位置也会相应改变。

2、实现

生成相反的遮罩图能够分为三步。假设一开始拿到的 maskView 是下面这样,让咱们来看下,转换过程当中遮罩图每一步的变化。

注: 为了更直观的效果,图片中透明的部分用灰白相间格子来表示(如下相同)。

一、将 maskView 转化为 UIImage

UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);
CGContextTranslateCTM(UIGraphicsGetCurrentContext(),
                      view.frame.origin.x,
                      view.frame.origin.y);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
复制代码

这一步拿到了 maskView 对应的 image 图像。此时遮罩图的大小会被同步为 originView 的大小。

二、将 UIImage 转换为只有 alpha 通道的 CGContextRef

CGImageRef originalMaskImage = [image CGImage];
float width = CGImageGetWidth(originalMaskImage);
float height = CGImageGetHeight(originalMaskImage);
    
int strideLength = ceil(width);
unsigned char * alphaData = calloc(strideLength * height, sizeof(unsigned char));
CGContextRef alphaOnlyContext = CGBitmapContextCreate(alphaData,
                                                      width,
                                                      height,
                                                      8,
                                                      strideLength,
                                                      NULL,
                                                      kCGImageAlphaOnly);
    
CGContextDrawImage(alphaOnlyContext, CGRectMake(0, 0, width, height), originalMaskImage);
复制代码

这时候的 alphaOnlyContext 对应的图像是下面这样,只保留了 alpha 通道。

三、将 CGContextRef 中的 alpha 值进行遍历转换

for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
        unsigned char val = alphaData[y*strideLength + x];
        val = 255 - val;
        alphaData[y*strideLength + x] = val;
    }
}
    
CGImageRef alphaMaskImage = CGBitmapContextCreateImage(alphaOnlyContext);
UIImage *result = [UIImage imageWithCGImage:alphaMaskImage];
复制代码

转换后,得到的 result 图像是:

因而,咱们就能够用 result 愉快地进行 mask 了。

3、使用

咱们能够将上述的步骤,封装为一个方法,用 category 来实现。

@interface UIView (MFSubtractMask)

- (void)setSubtractMaskView:(UIView *)view;

- (UIView *)subtractMaskView;

@end
复制代码

这样调用起来就十分方便了,一行代码搞定:

view.subtractMaskView = maskView;
复制代码

4、局限性

1. subtractMaskView 不会自动刷新

咱们知道,当 UIViewmaskView 的内容动态修改时,会实时反映到 UIView 中。但在本项目中, subtractMaskView 属性会生成一张全新的图片来做为遮罩图,由于不会根据 subtractMaskView 的内容实时来刷新视图。若是须要更新,必须手动调用 setSubtractMaskView: 方法来从新生成遮罩图。

2. setSubtractMaskView: 不宜被频繁调用

setSubtractMaskView: 本质上是生成一个新的遮罩图的过程,该过程涉及图片像素的遍历转换,较为耗时,不宜频繁调用。

综上所述,这种方案适合只生成一次遮罩图的场景。

5、源码

请到 GitHub 上查看完整代码。

参考

获取更佳的阅读体验,请访问原文地址 【Lyman's Blog】一行代码实现 UIView 镂空效果

相关文章
相关标签/搜索