一行代碼實現 UIView 鏤空效果

這是一種實現 UIView 鏤空效果的方案,可以快速實現任意形狀的鏤空、文字的鏤空、帶鏤空的毛玻璃效果等。本質上是 UIViewmaskView 效果的「取反」。

前言

首先來復習一下遮罩效果的實現。如果我們有一張圖片,又恰好有一個圓,當我們把圓設置為圖片的遮罩時,會得到這樣的結果。

代碼實現看上去像是這樣:

view.maskView = maskView;

那么問題來了,如果我們希望得到下面的結果,該怎么做呢?這看起來像是圖層的相減,即原來的圖層減去遮罩的部分。

可惜蘋果爸爸不夠貼心,沒有提供方便的接口調用。讓我們來看看可以怎么實現。

一、思路

我們的最終目標是,封裝出一個接口,調用方式類似于 maskView 屬性,可以很方便地對一個 UIView 做鏤空效果。

注:以下用 originView 指代需要上效果的 view,用 maskView 指代充當遮罩的 view

目前看來,可以從兩個方向入手:

  1. 修改遮罩的繪制過程
  2. 修改 maskView 本身

方式一是指,在設置這個屬性的時候,對 originView 的視圖進行重新繪制,然后在繪制的時候,減掉 maskView 的區域。

方式二是指,當拿到 maskView 的時候,先對 maskView 本身先進行處理,將遮罩范圍取反。然后再做遮罩效果,由于遮罩的區域已經相反,于是得到的結果也是相反的,就達到鏤空的目的。

看上去方式二比較靠譜,而且最后是調用 UIViewsetMaskView: 來實現,還可以保留原來遮罩的一些特性。比如當修改 maskViewframe 的時候, originView 的遮罩位置也會相應改變。

二、實現

生成相反的遮罩圖可以分為三步。假設一開始拿到的 maskView 是下面這樣,讓我們來看下,轉換過程中遮罩圖每一步的變化。

注:為了更直觀的效果,圖片中透明的部分用灰白相間格子來表示(以下相同)。

1、將 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 的大小。

2、將 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 通道。

3、將 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 了。

三、使用

我們可以將上述的步驟,封裝為一個方法,用 category 來實現。

@interface UIView (MFSubtractMask)

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

- (UIView *)subtractMaskView;

@end

這樣調用起來就十分方便了,一行代碼搞定:

view.subtractMaskView = maskView;

四、局限性

1. subtractMaskView 不會自動刷新

我們知道,當 UIViewmaskView 的內容動態修改時,會實時反映到 UIView 中。但在本項目中, subtractMaskView 屬性會生成一張全新的圖片來作為遮罩圖,因為不會根據 subtractMaskView 的內容實時來刷新視圖。如果需要更新,必須手動調用 setSubtractMaskView: 方法來重新生成遮罩圖。

2. setSubtractMaskView: 不宜被頻繁調用

setSubtractMaskView: 本質上是生成一個新的遮罩圖的過程,該過程涉及圖片像素的遍歷轉換,較為耗時,不宜頻繁調用。

綜上所述,這種方案適合只生成一次遮罩圖的場景。

五、源碼

請到 GitHub 上查看完整代碼。

參考

Quartz 2D編程指南之十一:位圖與圖像遮罩
How to convert UIImage/CGImageRef's alpha channel to mask?

獲取更佳的閱讀體驗,請訪問原文地址 【Lyman's Blog】一行代碼實現 UIView 鏤空效果

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容