CALayer remove transparent masking - ios

I am working on an ios application and i have a issue using CALayer it has created a transparent mask on every image i put in the Layer.
Please have a look at the code below :
self.mask = [CALayer layer];
self.mask.contents = CFBridgingRelease(([UIImage imageNamed:#"launch screen.png"].CGImage));
self.mask.bounds = CGRectMake(0, 0, 200, 200);
self.mask.anchorPoint = CGPointMake(0.5, 0.5);
self.mask.position = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds));
self.imageView = _imageView;
self.view.layer.mask = self.mask;
Please Refer to this image for example :
http://31.media.tumblr.com/10cc0ba92377a2cba9fb35c9943fd2ca/tumblr_inline_n6zpokNxpC1qh9cw7.gif

Related

CALayer draw outside of rect

I'm using this code to add a layer to a custom UIView and it works like a charm:
CGRect newrect = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
CALayer* heartBackground = [CALayer layer];
heartBackground.contents = (__bridge id)([UIImage imageNamed:#"5HeartsGray"].CGImage);
heartBackground.frame = newrect;
[self.layer addSublayer:heartBackground];
But when I tried to use it in draw method using Quartz using a new Rect(like this)
CGContextSaveGState(context);
CGRect ratingRect = CGRectMake(250, 100, 150, 20);
CALayer* heartBackground = [CALayer layer];
heartBackground.contents = (__bridge id)([UIImage imageNamed:#"5HeartsGray"].CGImage);
heartBackground.frame = ratingRect;
[heartBackground renderInContext:context];
CGContextRestoreGState(context);
It renders in the beginning of the frame and not inside of ratingRect.
If I call [heartBackground setNeedsDisplay] it disappear. The same thing with heartBackground.masksToBounds = YES
What I'm doing wrong. Do I need To switch to CGLayer because I'm using CoreGraphics?
This is the output when working with CoreGraphics (as you see the hearts starts at coordinates x=0, y=0 and normally it starts at x=250, y=100):
It doesn't make sense to create and draw a CALayer inside draw function of a UIView. CALayer is an independently drawable unit and it draws itself on its parentLayer's context by default. Which means if you really want to use separate CALayers try adding as many subLayers to self.layer (inside a UIView) as you want, set their frames and they'll get drawn when their container view shows up. Regarding your problem, here's what you can try.
Option 1: Take this code
CGRect ratingRect = CGRectMake(250, 100, 150, 20);
CALayer* heartBackground = [CALayer layer];
heartBackground.contents = (__bridge id)([UIImage imageNamed:#"5HeartsGray"].CGImage);
heartBackground.frame = ratingRect;
[self.layer addSubLayer:heartBackground];
Out of the drawRect: method and write it in initWithFrame: and in drawRect simply do [heartBackground renderInContext:UIGraphicsGetCurrentContext];
Option 2: Draw your hearts without a CALayer.
CGRect ratingRect = CGRectMake(250, 100, 150, 20);
[[UIImage imageNamed:#"5HeartsGray"] drawInRect:ratingRect];

CALayer Troubles when loading an image from a UIImagePicker

I have a UIViewController with a UIImageView (imageView), and I'm defining several layers that will be nested in the imageview as follows in viewDidLoad:
//container layer - the very top layer
CALayer *containerLayer = [CALayer layer];
containerLayer.opacity = 0;
containerLayer.bounds = [self.imageView.layer frame];
// Holder Layer
CALayer *holderLayer = [CALayer layer];
holderLayer.opacity = 0;
holderLayer.bounds = self.imageView.bounds;
// Hierarchy layers
[containerLayer setValue:holderLayer forKey:#"__holderLayer"];
[containerLayer addSublayer:holderLayer];
[self.imageView.layer addSublayer:containerLayer];
I have the following code that works when loading an image from a UIImagePicker :
UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
UIImageView *newImgView = [[UIImageView alloc] initWithFrame:self.imageView.frame];
CGRect frame = self.imageView.frame;
newImgView.image = image;
frame.origin = CGPointMake(0, 0);
newImgView.layer.frame = frame;
newImgView.layer.opacity = .9;
newImgView.layer.contentsGravity = kCAGravityResizeAspectFill;
CALayer * containerLayer = self.imageView.layer.sublayers[0];
if (containerLayer != nil)
{
[containerLayer setValue:newImgView.layer forKey:#"__imageLayer"];
CALayer * holderLayer = [containerLayer valueForKey:#"__holderLayer"];
if (holderLayer != nil)
{
//!!!!!line below doesn't work!!!
//[holderLayer addSublayer:newImgView.layer];
//line below works!
[self.imageView.layer addSublayer:newImgView.layer];
}
}
[self.imageView setNeedsDisplay];
[self checkAndPrintLayers];
so the first nested layer is containerLayer, then holderLayer, and I'm expecting to add various images as sublayers to the holderLayer and then manipulate it. However, calling
[holderLayer addSublayer:newImgView.layer];
doesn't work; the imageView stays blank. However, calling
[self.imageView.layer addSublayer:newImgView.layer];
and adding a sublayer to the top layer works just dandy.
Am I missing something obvious here? Would love any suggestions.
thanks.
Because
holderLayer and containerLayer are transparent (holderLayer.opacity = 0 / containerLayer.opacity = 0),
Make it,
//container layer - the very top layer
CALayer *containerLayer = [CALayer layer];
containerLayer.opacity = 1.0;
containerLayer.bounds = [self.imageView.layer frame];
// Holder Layer
CALayer *holderLayer = [CALayer layer];
holderLayer.opacity = 1.0;
holderLayer.bounds = self.imageView.bounds;

CALayer hides the imageView

Hai all am using CALayer to mask the UIImage, when i add the layer on imageview it display only layer portion hides remanning all image with white color filled
CALayer *mask = [CALayer layer];
mask.contents = (id)[[UIImage imageNamed:#"mask.png"] CGImage];
mask.frame = CGRectMake(0, 0, mainImageWidth+30, mainImageHeight);
mask.shadowOffset = CGSizeMake(0, 3);
mask.shadowOpacity = 1.5;
mainImageView.layer.mask = mask;
[mainImageView.layer setMasksToBounds:NO];
it hides my image view except the layer portion. how do i solve this
Instead of create mask using image view i have added three mask for UIView now its working perfect
mainLayer.frame = CGRectMake(0, 0, mainImageWidth, mainImageHeight);
[self.view.layer addSublayer:mainLayer];
mainLayer.backgroundColor = [UIColor clearColor].CGColor;
secondLayer.frame = CGRectMake(gloss_x, gloss_y, gloss_w, gloss_h);
maskLayer = [CALayer layer];
UIImage *mask = [UIImage imageNamed:#"mask.png"];
maskLayer.contents = (id)mask.CGImage;
maskLayer.frame = maskRect;//CGRectMake(gloss_x, gloss_y-50, gloss_w, gloss_h);
secondLayer.contents = (id)s_glossImage.CGImage;
[mainLayer addSublayer:secondLayer];
mainLayer.mask = maskLayer;
[mainImageView.layer addSublayer:mainLayer];
Now its working fine.

Masking a CALayer with another CALayer

I'm trying to make a donut shape with CALayers. One CALayer will be a large circle, the other one will be a smaller circle positioned in its center, masking it.
The large circle displays fine, but whenever I call circle.mask = circleMask; then the view appears empty.
Here's my code:
AriDonut.h
#import <UIKit/UIKit.h>
#interface AriDonut : UIView
-(id)initWithRadius:(float)radius;
#end
AriDonut.m
#import "AriDonut.h"
#import <QuartzCore/QuartzCore.h>
#implementation AriDonut
-(id)initWithRadius:(float)radius{
self = [super initWithFrame:CGRectMake(0, 0, radius, radius)];
if(self){
//LARGE CIRCLE
CALayer *circle = [CALayer layer];
circle.bounds = CGRectMake(0, 0, radius, radius);
circle.backgroundColor = [UIColor redColor].CGColor;
circle.cornerRadius = radius/2;
circle.position = CGPointMake(radius/2, radius/2);
//SMALL CIRLCE
CALayer *circleMask = [CALayer layer];
circleMask.bounds = CGRectMake(0, 0, 10, 10);
circleMask.cornerRadius = radius/2;
circleMask.position = circle.position;
//circle.mask = circleMask;
[self.layer addSublayer:circle];
}
return self;
}
I've tried setting the large circle's superlayer nil like this:
CALayer *theSuper = circle.superlayer;
theSuper = nil;
But it didin't make a difference.
I also tried setting Circle's masksToBounds property to YES and NO, but it didn't make a difference.
Any thoughts?
Indeed, as #David indicates the current (iOS 5.1) CALayer masks can't be reversed, which poses a problem if you want to use them to make a transparent hole a simple circular CALayer.
What you can do to get a donut is make a circular CALayer's backgroundColor transparent, but give it a borderColor and a wide borderWidth. Here's the dunkin' code:
CALayer *theDonut = [CALayer layer];
theDonut.bounds = CGRectMake(0,0, radius, radius);
theDonut.cornerRadius = radius/2;
theDonut.backgroundColor = [UIColor clearColor].CGColor;
theDonut.borderWidth = radius/5;
theDonut.borderColor = [UIColor orangeColor].CGColor;
[self.layer addSublayer:theDonut];
This is pretty easy using UIBezierPath and a CAShapeLayer as a masking layer. Code sample written as though it's in a UIView subclass.
Objective-C:
CGRect outerRect = self.bounds;
CGFloat inset = 0.2 * outerRect.size.width; // adjust as necessary for more or less meaty donuts
CGFloat innerDiameter = outerRect.size.width - 2.0 * inset;
CGRect innerRect = CGRectMake(inset, inset, innerDiameter, innerDiameter);
UIBezierPath *outerCircle = [UIBezierPath bezierPathWithRoundedRect:outerRect cornerRadius:outerRect.size.width * 0.5];
UIBezierPath *innerCircle = [UIBezierPath bezierPathWithRoundedRect:innerRect cornerRadius:innerRect.size.width * 0.5];
[outerCircle appendPath:innerCircle];
CAShapeLayer *maskLayer = [CAShapeLayer new];
maskLayer.fillRule = kCAFillRuleEvenOdd; // Going from the outside of the layer, each time a path is crossed, add one. Each time the count is odd, we are "inside" the path.
maskLayer.path = outerCircle.CGPath;
self.layer.mask = maskLayer;
Swift:
let outerRect = self.bounds
let inset: CGFloat = 0.2 * outerRect.width // adjust as necessary for more or less meaty donuts
let innerDiameter = outerRect.width - 2.0 * inset
let innerRect = CGRect(x: inset, y: inset, width: innerDiameter, height: innerDiameter)
let outerCircle = UIBezierPath(roundedRect: outerRect, cornerRadius: outerRect.width * 0.5)
let innerCircle = UIBezierPath(roundedRect: innerRect, cornerRadius: innerRect.width * 0.5)
outerCircle.appendPath(innerCircle)
let mask = CAShapeLayer()
mask.fillRule = kCAFillRuleEvenOdd
mask.path = outerCircle.CGPath
self.layer.mask = mask
It is the alpha value of the masking layers content that is used as a mask. (If you would add the mask as a sublayer instead of using it as a mask. Everything that is covered by the sublayer would be visible when used as a mask. Everything that is not covered by the sublayer would be hidden when used as a mask.)
Since your small circle is fully transparent , everything is masked away (is hidden). If you set the backgroundColor of it to any, fully opaque color (only the alpha value is used for the mask) then it will let those pixels through.
Note that this is the reverse of what you want. This will leave you with only "the hole of the donut" visible. There is no built in way to do a reverse mask Instead you would have to draw the content of the mask some other way like using a CAShapeLayer or using drawInContext:.
I succeeded with a CAShapeLayer masking a CALayer. To specify the shape of the masking CAShapeLayer I used UIBezierPath.
I posted the code in my answer to this question: How to Get the reverse path of a UIBezierPath. For the donut shape uncomment the commented line.

Display an image or UIImage with a plain CALayer

I've often read that using a CALayer rather than a UIImageView is an performance boost when it comes to heavy image usage. That makes sense, because UIImageView causes 3 copies of the image in memory, which is needed for Core Animation. But in my case I don't use Core Animation.
How can I assign a UIImage (or its image data) to a CALayer and then display it?
UIImage* backgroundImage = [UIImage imageNamed:kBackName];
CALayer* aLayer = [CALayer layer];
CGFloat nativeWidth = CGImageGetWidth(backgroundImage.CGImage);
CGFloat nativeHeight = CGImageGetHeight(backgroundImage.CGImage);
CGRect startFrame = CGRectMake(0.0, 0.0, nativeWidth, nativeHeight);
aLayer.contents = (id)backgroundImage.CGImage;
aLayer.frame = startFrame;
or in a Swift playground (you will have to provide your own PNG image in the Playground's resource file. I'm using the example of "FrogAvatar".)
//: Playground - noun: a place where people can play
import UIKit
if let backgroundImage = UIImage(named: "FrogAvatar") // you will have to provide your own image in your playground's Resource file
{
let height = backgroundImage.size.height
let width = backgroundImage.size.width
let aLayer = CALayer()
let startFrame = CGRect(x: 0, y: 0, width: width, height: height)
let aView = UIView(frame: startFrame)
aLayer.frame = startFrame
aLayer.contentsScale = aView.contentScaleFactor
aLayer.contents = backgroundImage.cgImage
aView.layer.addSublayer(aLayer)
aView // look at this via the Playground's 👁 eye icon
}
ARC version requires a different cast:
self.myView.layer.contents = (__bridge id) self.myImage.CGImage;
CALayer *layer = [[CALayer alloc] init];
layer.contents = (__bridge id _Nullable)([UIImage imageNamed:#"REWIFISocketOff"].CGImage);

Resources