CALayer containsPoint with mask not working - ios

I have a UIView that have a CALayer mask:
// Getting the right mask image
UIImage *myimage = [UIImage imageNamed:[NSString stringWithFormat:#"img%d", imageIndex]];
// Scaling image to fit UIView
UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, 0.0);
[myimage drawInRect:self.bounds];
myimage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.layer setMasksToBounds:YES];
// Setting mask
UIImage *_maskingImage = myimage;
CALayer *_maskingLayer = [CALayer layer];
_maskingLayer.frame = self.bounds;
[_maskingLayer setContents:(id)[_maskingImage CGImage]];
[self.layer setMask:_maskingLayer];
When the user later short press (UILongPressGestureRecognizer *) the UIView I just want a action to happend if the user taps in the UIView layer mask (for now containsPoint always returns YES):
// Object is a UIView
CALayer *layer = [Object.layer mask];
// Sender is a UILongPressGestureRecognizer
location2 = [sender locationInView:Object];
// The position is correct
NSLog(#"%#", NSStringFromCGPoint(location2));
if ([layer containsPoint:location2]){
NSLog(#"HELLO WORLD!");
return;
}
Help, please?

My book gives two suggestions. If you know the bounding path of the mask drawing, you can use CGPathContainsPoint. Or, if the layer in question is transparent on the outside and opaque on the inside, you can examine the pixel at the tapped location to see if it is transparent or not.
http://www.apeth.com/iOSBook/ch18.html#_hit_testing
Scroll down to the section "Hit-testing for drawings".

Related

How to draw image inside a circular bezier path

I am trying to create a custom view to draw a music CD with the album artwork on the CD.
Currently I use core graphics to draw two circular disks inside the drawRect method. The inner disk's colour is set to the background colour of the view to make it look like the inner circle is a hollow circle.
My problem is that I can't draw the UIImage inside the the circular bezier path to get the result that I need.
Here's my drawRect code:
// Draw a CD like view using three drawing operations:
// 1st draw the main disk
// 2nd draw the image if it is set
// 3rd draw the inner disk over the image so that it looks like it is a hollow disk with the image painted on the disk.
// Note:) This view expects its aspect ratio to be set as 1:1
- (void)drawRect:(CGRect)rect
{
// Draw the main Circular CD disk
UIBezierPath* outerRing = [UIBezierPath bezierPathWithOvalInRect:self.bounds];
[self.mainColor setFill];
[outerRing fill];
[self.outerRingColor setStroke];
[outerRing stroke];
// Draw the album image if it is set
if(self.artwork){
[self.artwork drawInRect:self.bounds blendMode:kCGBlendModeNormal alpha:1.0];
}
// now draw another smaller disk inside
// this will be a percentage smaller than the whole disk's bounds and will be centered inside it
CGFloat sidePaddingCoord = ((1.0f - INNER_DISK_SIZE_PERCENTAGE) / 2) * self.bounds.size.height;
CGFloat side = INNER_DISK_SIZE_PERCENTAGE * self.bounds.size.width;
UIBezierPath* innerRing = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(sidePaddingCoord, sidePaddingCoord, side, side)];
[self.innerRingColor setFill];
[innerRing fill];
[innerRing stroke];
}
This fills the image as a square. I want the image to be clipped inside the outerRing bezier path so that it looks like a music CD.
I'm sure there is a way to use core graphics to achieve something else besides using the image's drawInRect method. Is there any way to 'clip' the image inside the circular bezier path or only draw inside the bezier path?
I've read really great posts by rob mayoff, UIBezierPath Subtract Path, iOS UIImage clip to paths, many thanks.
Here is an adoption of his code. Keep your creation code of outerRing and innerRing, add them to an array named paths.
[self.paths addObject:outerRing];
[self.paths addObject:innerRing];
Then use this help method.
- (UIImage *)maskedImage
{
CGRect rect = CGRectZero;
rect.size = self.originalImage.size;
UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0.0);
UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:CGRectInfinite];
[clipPath appendPath:self.paths[1]];
clipPath.usesEvenOddFillRule = YES;
CGContextSaveGState(UIGraphicsGetCurrentContext()); {
[clipPath addClip];
[[UIColor orangeColor] setFill];
[self.paths[0] fill];
} CGContextRestoreGState(UIGraphicsGetCurrentContext());
UIImage *mask = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0); {
CGContextClipToMask(UIGraphicsGetCurrentContext(), rect, mask.CGImage);
[self.originalImage drawAtPoint:CGPointZero];
}
UIImage *maskedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return maskedImage;
}
In drawRect,
UIImage *maskedImage = [self maskedImage];
[maskedImage drawInRect:self.bounds blendMode:kCGBlendModeNormal alpha:1.0];
Try below code:
UIImageView *userImageView= [[UIImageView alloc] initWithFrame:CGRectMake(85, 55.0, 90, 90)];
userImageView.layer.cornerRadius = 90/2;
userImageView.clipsToBounds = YES;
userImageView.layer.borderWidth = 1.0f;
userImageView.layer.borderColor = [UIColor blueColor];
[self.view addSubview:userImageView]; //or add it to the view you want
UIImage *userImage = [UIImage imageNamed:#"<image you want to set>"];
userImageView= [[UIImageView alloc] initWithFrame:CGRectMake(90, 60.0, 80, 80)];
userImageView.image = userImage;
userImageView.layer.cornerRadius = 80/2;
userImageView.clipsToBounds = YES;
userImageView.layer.borderWidth = 1.0f;
userImageView.layer.borderColor = [UIColor blueColor;
[self.view addSubview:userImageView]; //or add it to the view you want

iOS - Make UIImage inside Button Round

Everybody loves them some round profile images these days. So I guess I do too. With that said...
I have a UIImage set as the background image of a UIButton. What I need to do is make the profile picture completely rounded. Here's my code ... can anyone point me in the right direction?
NSString* photoUrl = [NSString stringWithFormat:#"%#%#", URL_IMAGE_BASE, photoFileName];
NSURL *myurl2 = [NSURL URLWithString: photoUrl];
UIImage *image = [UIImage imageWithData: [NSData dataWithContentsOfURL:myurl2]];
[_goToProfileEditButton setImage:image forState:UIControlStateNormal];
[_goToProfileEditButton setBackgroundImage:image forState:UIControlStateNormal];
Make the button a perfect square (same width/height) and use the cornerRadius property of the button layer and set it to be exactly half its width/height. You need to import the QuartzCore header to be able to access the layer property.
What you could do is subclass UIView and override the drawRect: method like so:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
CGContextAddEllipseInRect(context, rect);
CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
CGContextFillPath(context);
}
Set the view's clipsToBounds property to YES and add your image view as a subview of your subclassed UIView. Otherwise you could look into masking with an image via CGImageMaskCreate.
bolivia's answer is the best if you are happy to mask the button itself.
If you want to create a circle image from a square, then you can do that with a clipping mask. It should be something like as follows:
// start with the square image, from a file, the network, or wherever...
UIImage * squareImage = [UIImage imageNamed:#"squareImage.png"];
CGRect imageRect = CGRectMake(0, 0, squareImage.size.width, squareImage.size.height);
UIGraphicsBeginImageContextWithOptions(imageRect.size,NO,0.0);
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:imageRect cornerRadius:imageRect.size.width/2.0f];
[path addClip];
[squareImage drawInRect:imageRect];
UIImage *circleImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// now circleImage contains the circle in the center of squareImage

Drawing sprites programatically for sprite kit with alpha channel?

I'm trying to prototype an app for sprite kit, and it would be advantageous to me to be able to draw the sprites I need programatically for now, and then make actual sprites later. I've made a rough circle, and when I convert to a UIImage (or CGImage) using renderInContext, it shows up on the sprite view with a black background, ignoring the alpha channel, apparently. If I add the UIImage to a regular view as a UIImageView, the alpha channel is rendered properly. I was wondering what I'm doing wrong, or if theres' a workaround for this.
float rockSize = 100;
UIView* drawingView = [[UIView alloc] initWithFrame:CGRectMake(0,0,100,100)];
drawingView.backgroundColor = [UIColor clearColor];
CALayer* circleLayer = [CALayer layer];
circleLayer.frame = CGRectMake(0,0,rockSize,rockSize);
circleLayer.backgroundColor = [Utils getTiledColorFromColor:[UIColor purpleColor]].CGColor;
circleLayer.cornerRadius = rockSize/2.0;
circleLayer.masksToBounds = YES;
[drawingView.layer addSublayer:circleLayer];
UIGraphicsBeginImageContextWithOptions(CGSizeMake(rockSize, rockSize), NO, [UIScreen mainScreen].scale);
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [UIColor clearColor].CGColor);
CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(0,0,rockSize,rockSize));
[drawingView.layer renderInContext: UIGraphicsGetCurrentContext()];
UIImage *layerImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
SKSpriteNode *rockNode = [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImage:layerImage]];
CGPoint position = CGPointMake(200,100);
rockNode.position = position;
[self addChild:rockNode];
Such an affect when you see black view background frequently happens when the view has opaque property incorrectly set to YES without actually being opaque (usually this happens when the view is created programmatically, opaque = YES is default).
You can fix this for example by setting opaque to NO.

Transparent Circle at Middle of UIImageView IOS

I need to make UIImageView transparent at the Middle Position(Round Circle ..See attached image)
I should be able to see the Behind Subviews of UIImageView via that middle circle...i tried CAShapeLayer..but i am not able to see the behind subviews of UIImageView..Please help me..Sorry for my English....
CAShapeLayer *circleLayer = [CAShapeLayer layer];
// Give the layer the same bounds as your image view
[circleLayer setBounds:CGRectMake(100.0f, 100.0f, [((UIImageView *)view) bounds].size.width,
[((UIImageView *)view) bounds].size.height)];
// Position the circle anywhere you like, but this will center it
// In the parent layer, which will be your image view's root layer
[circleLayer setPosition:CGPointMake(100,
100)];
// Create a circle path.
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:
CGRectMake(100.0f, 100.0f, 50.0f, 50.0f)];
// Set the path on the layer
[circleLayer setPath:[path CGPath]];
// Set the stroke color
[circleLayer setStrokeColor:[[UIColor redColor] CGColor]];
// Set the stroke line width
[circleLayer setLineWidth:2.0f];
// Add the sublayer to the image view's layer tree
[circleLayer setOpacity:0.5];
[[((UIImageView *)view) layer] addSublayer:circleLayer];
One way to do it is like this. I have an image view with my main image, and another image called BlackCircle which is just a 40x40 black circle (that I made in the Pixen app). The action method causes the main image to become transparent where it is overlapped by the black circle, which I've placed in the center of the main image:
#interface ViewController ()
#property (weak,nonatomic) IBOutlet UIImageView *imageView;
#end
#implementation ViewController
-(void)viewDidLoad {
[super viewDidLoad];
self.imageView.image = [UIImage imageNamed:#"House.tiff"];
self.imageView.backgroundColor = [UIColor clearColor];
}
-(IBAction)createAlphaClippedImage:(id)sender {
UIImage *circleImage = [UIImage imageNamed:#"BlackCircle.png"];
UIImage *mainImage = [UIImage imageNamed:#"House.tiff"];
UIGraphicsBeginImageContextWithOptions(mainImage.size, NO, 0.0);
CGRect imageRect = CGRectMake(0.0f, 0.0f, mainImage.size.width, mainImage.size.height);
CGRect centerCircleRect = CGRectMake(mainImage.size.width/2.0 - circleImage.size.width/2.0, mainImage.size.height/2.0 - circleImage.size.height/2.0,circleImage.size.width,circleImage.size.height);
[mainImage drawInRect:imageRect];
[circleImage drawInRect:centerCircleRect blendMode:kCGBlendModeXOR alpha:1.0];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.imageView.image = image;
}
Instead of trying to make a portion of Imageview transparent, try to make in image transparent at areas you want it to by setting corresponding alphas to 0.

Using renderInContext: for masks created by cgpaths

I recently found out that the renderinContext: method ignores any layer masks. I am trying to capture a video of my app's screen, in which I animate the path of a masked CALayer.
To record the screen, I am using the ScreenCaptureView class, found online. The view takes a "screenshot" of it's subviews every x seconds and compiles them into a video.
In the drawRect method, I call
[[self.layer presentationLayer] renderInContext:context];
After looking online, I found someone's solution; however, it applies for masks that are images. (My mask is a CGPath).
-(UIImage*) imageFromView:(UIView*)originalView
{
//Creating a fakeView which is initialized as our original view.
//Actually i render images on a fakeView and take screenshot of this fakeView.
UIView *fakeView = [[UIView alloc] initWithFrame:originalView.frame];
[fakeView.layer setMasksToBounds:originalView.layer.masksToBounds];
//Getting subviews of originalView.
for (UIView *view in originalView.subviews)
{
//Getting screenshot of layer of view.
UIGraphicsBeginImageContext(view.bounds.size);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *imageLayer = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//If it is masked view, then get masked image.
if (view.layer.mask)
{
//getting screenshot of masked layer.
UIGraphicsBeginImageContext(view.bounds.size);
[view.layer.mask renderInContext:UIGraphicsGetCurrentContext()];
//PNG Representation is most important. otherwise this masked image will not work.
UIImage *maskedLayerImage=[UIImage imageWithData:UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext())];
UIGraphicsEndImageContext();
//getting image by masking original image.
imageLayer = [self maskImage:imageLayer withMask:maskedLayerImage];
}
//If imageLayer is pointing to a valid object, then setting this image to UIImageView, our UIImageView frame having exactly as view.frame.
if (imageLayer)
{
UIImageView *imageView = [[UIImageView alloc] initWithFrame:view.frame];
[imageView setImage:imageLayer];
[fakeView addSubview:imageView];
}
}
//At the end, taking screenshot of fakeView. This will get your Original Image.
UIGraphicsBeginImageContext(fakeView.bounds.size);
[fakeView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *previewCapturedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return previewCapturedImage;
}
}
//Method is used to get masked image.
- (UIImage*) maskImage:(UIImage *)image withMask:(UIImage *)maskImage
{
CGImageRef maskRef = maskImage.CGImage;
CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
CGImageGetHeight(maskRef),
CGImageGetBitsPerComponent(maskRef),
CGImageGetBitsPerPixel(maskRef),
CGImageGetBytesPerRow(maskRef),
CGImageGetDataProvider(maskRef), NULL, false);
CGImageRef masked = CGImageCreateWithMask([image CGImage], mask);
return [UIImage imageWithCGImage:masked];
}
Can I do something similar for masks from cgpaths?
You can use CGContextClip() in renderInContext: to clip to a path:
- (void)renderInContext:(CGContextRef)context {
// Assign maskPath to whatever path you want to mask to (Here, it's assumed that your layer's mask is a CAShapeLayer)
CGPathPathRef maskPath = [(CAShapeLayer *)self.mask path];
CGContextAddPath(context, maskPath);
CGContextClip(context);
// Do drawing code here
}

Resources