27

I'm working on an app where I draw a UIView via code and I fill it with a UIColor. Next thing I want to load a mask.png file (no transparency, just black and white) and mask the UIView to change its visual appearance.

Any idea how to do this?

bneely
  • 9,083
  • 4
  • 38
  • 46
Thomas Joos
  • 2,451
  • 5
  • 26
  • 30

6 Answers6

52
// Create your mask layer
CALayer* maskLayer = [CALayer layer];
maskLayer.frame = CGRectMake(0,0,yourMaskWidth ,yourMaskHeight);
maskLayer.contents = (__bridge id)[[UIImage imageNamed:@"yourMaskImage.png"] CGImage];

// Apply the mask to your uiview layer        
yourUIView.layer.mask = maskLayer;

Remember to import QuartzCore and add the framework

#import <QuartzCore/QuartzCore.h>

It is very easy but I didn't find the answer anywhere!

JEzu
  • 2,949
  • 1
  • 16
  • 6
  • 1
    Any answer that uses Core Animation instead of Core Graphics should be preferred. CA's API is much easier to understand and use. – zakdances Apr 24 '13 at 05:15
12

iOS 8 introduces the maskView property which allows you to use an alternate view to provide the mask image. In Swift:

if let maskImage = UIImage(named: "MaskImage") {
    myView.maskView = UIImageView(image: maskImage)
}

Note that you need to supply an image which actually has transparency; an image with white parts (for opaque) and black parts (for transparent) won't work.

Robert
  • 5,735
  • 3
  • 40
  • 53
  • Any idea why this might crash with EXC_BAD_ACCESS? (-[UIImageView _maskView]: message sent to deallocated instance) – Yaroslav May 07 '16 at 18:17
5

I have create a category with the code above :

UIView+ImageMask.h :

#import <UIKit/UIKit.h>

@interface UIView (ImageMask)

- (void) maskWithImage:(UIImage*) mask;
- (void) maskWithImage:(UIImage*)mask size:(CGSize)maskSize;
- (void) removeMask;

@end

UIView+ImageMask.m :

#import <QuartzCore/QuartzCore.h>

#import "UIView+ImageMask.h"

@implementation UIView (ImageMask)

- (void) maskWithImage:(UIImage*) mask {
    [self maskWithImage:mask size:mask.size];
}

- (void) maskWithImage:(UIImage*)mask size:(CGSize)maskSize {
    CALayer* maskLayer = [CALayer layer];
    maskLayer.frame = CGRectMake(0, 0, maskSize.size.width, maskSize.size.height);
    maskLayer.contents = (__bridge id)[mask CGImage];

    // Apply the mask to your uiview layer
    self.layer.mask = maskLayer;
}

- (void) removeMask {
    self.layer.mask = nil;
}

@end
Pion
  • 708
  • 9
  • 21
mt81
  • 3,288
  • 1
  • 26
  • 35
  • What kind of image do you have in input ? black& white or translucent and white or translucent and black ? – Vassily Feb 20 '14 at 16:53
  • I used a black image with alpha. But I don't think this will make a difference. If you try it, let me know :). – mt81 Feb 20 '14 at 20:16
  • I tried. Actually have a difference. Only alpha component will make effect – Arsynth Feb 27 '14 at 15:09
  • yes, image needs to be PNG with alpha component. only the alpha will be masked, the rest can be white or black it doesn't matter – Vassily Feb 28 '14 at 14:45
5

You might be able to use CGContextClipToMask:

-(void) drawRect:(CGRect)dirty {
  CGContextRef context = UIGraphicsGetCurrentContext();
  CGContextSaveGState( context );
  CGContextClipToMask( context , [self bounds] , [self maskImage] );
  [super drawRect:dirty]; // or draw your color manually
  CGContextRestoreGState( context );
}
drawnonward
  • 53,459
  • 16
  • 107
  • 112
3

Thanks drawnonward,

ended up with this function:

- (UIImage*) maskImage:(UIView *)image withMask:(UIImage *)maskImage {

    UIGraphicsBeginImageContext(image.bounds.size);
    [image.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();


    CGImageRef maskRef = maskImage.CGImage; 

    CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
                                        CGImageGetHeight(maskRef),
                                        CGImageGetBitsPerComponent(maskRef),
                                        CGImageGetBitsPerPixel(maskRef),
                                        CGImageGetBytesPerRow(maskRef),
                                        CGImageGetDataProvider(maskRef), NULL, false);

    CGImageRef masked = CGImageCreateWithMask([viewImage CGImage], mask);
    return [UIImage imageWithCGImage:masked];

}
Thomas Joos
  • 2,451
  • 5
  • 26
  • 30
0

Swift 2.0

Pass a UIImage and a mask image to this function, and you will get a masked image as a UIImage.

func maskImage(image:UIImage, mask:(UIImage))->UIImage{

    let imageReference = image.CGImage
    let maskReference = mask.CGImage

    let imageMask = CGImageMaskCreate(CGImageGetWidth(maskReference),
                                  CGImageGetHeight(maskReference),
                                  CGImageGetBitsPerComponent(maskReference),
                                  CGImageGetBitsPerPixel(maskReference),
                                  CGImageGetBytesPerRow(maskReference),
                                  CGImageGetDataProvider(maskReference), nil, true)

   let maskedReference = CGImageCreateWithMask(imageReference, imageMask)

   let maskedImage = UIImage(CGImage:maskedReference!)

   return maskedImage
}
Pang
  • 9,564
  • 146
  • 81
  • 122
Ashutosh Jha
  • 150
  • 4
  • 18