I am using MKNetwrkKit. I have an imageView, I have added it as a subView to a UIView. I download an image using "UIImageView+MKNetworkKitAdditions.h". I add a mask to the ImageView and remove it from superView, before the download is completed, and I get a crash!
- (void) testUIImageViewDownloadCrash {
UIImageView *imageView = [[UIImageView alloc] init];
[imageView mk_setImageAtURL:[NSURL URLWithString:#"http://www.gravatar.com/avatar/a717291747c76567bb0f086e15ae6e43?s=32&d=identicon&r=PG"] onCompletion:^(BOOL success, BOOL fromCache) {
//
}];
UIView *view = [[UIView alloc] init];
[view addSubview:imageView];
[self addMaskToImageView:imageView];
[imageView removeFromSuperview];
}
Now, the weird thing is, this crash occurs only when I add the mask... here is the function that adds the mask.
- (CAGradientLayer*) addMaskToImageView:(UIImageView *) inImageView
{
CALayer *imageLayer = inImageView.layer;
CGRect imageBounds = imageLayer.bounds;
CGRect ellipseRect = CGRectInset(imageBounds, 4, 4);
CGPathRef maskPath = CGPathCreateWithEllipseInRect(ellipseRect, NULL);
if (maskPath)
{
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.bounds = imageBounds;
maskLayer.position = (CGPoint){CGRectGetMidX(imageBounds), CGRectGetMidY(imageBounds)};
maskLayer.path = maskPath;
maskLayer.fillColor = [UIColor blackColor].CGColor;
imageLayer.mask = maskLayer;
CFRelease(maskPath);
}
CAGradientLayer *gradientRingLayer = [self createGradientRing:imageBounds];
CALayer *containerLayer = [CALayer layer];
[imageLayer.superlayer addSublayer:containerLayer];
// Move container layer to the image position
containerLayer.position = imageLayer.position;
// Image is now at 0,0 in the container
imageLayer.position = CGPointZero;
[containerLayer addSublayer:gradientRingLayer];
[containerLayer addSublayer:imageLayer];
layer.shouldRasterize = YES;
layer.rasterizationScale = [[UIScreen mainScreen] scale];
layer.contentsScale = [[UIScreen mainScreen] scale];
containerLayer.shadowColor = [UIColor blackColor].CGColor;
containerLayer.shadowOpacity = 1.0f;
containerLayer.shadowOffset = CGSizeMake(2.0f, 5.0f);
containerLayer.shadowRadius = 7.5f;
// Create shadow at the righ place around the image
CGPathRef containerShadowPath = CGPathCreateWithEllipseInRect(imageLayer.frame, NULL);
containerLayer.shadowPath = containerShadowPath;
CFRelease(containerShadowPath);
return gradientRingLayer;
}
- (CAGradientLayer*)createGradientRing:(CGRect)inBounds {
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.anchorPoint = CGPointMake(0.5f, 0.5f);
gradient.position = CGPointMake(0.0f, 0.0f);
// gradient.startPoint = CGPointMake(0.0,0.0);
// gradient.endPoint = CGPointMake(0.0,0.0);
gradient.bounds = inBounds;
gradient.cornerRadius = 0.0;
gradient.colors = [NSArray arrayWithObjects:
(id)[UIColor ringLight].CGColor,
(id)[UIColor ringLight].CGColor,
nil];
CAShapeLayer *frameLayer = [CAShapeLayer layer];
CGPathRef framePath = CGPathCreateWithEllipseInRect(CGRectInset(inBounds, 0, 0), NULL);
if (framePath)
{
frameLayer.bounds = inBounds;
frameLayer.position = (CGPoint){CGRectGetMidX(inBounds), CGRectGetMidY(inBounds)};
frameLayer.path = framePath;
frameLayer.fillColor = [UIColor blackColor].CGColor;
CFRelease(framePath);
}
gradient.mask = frameLayer;
return gradient;
}
If I comment out adding the mask, it won't crash. Another thing that I don't understand is, after adding the mask, if do this:
NSLog(#"subviews count : %i",[view.subviews count]);
then I get 0! Where did the imageView go?
I observed that the addMask function is making a superView of the imageView null. I found out that the line [containerLayer addSublayer:imageLayer]; is the culprit. This is making the superView of the imageView null. But why? And how can I fix it?
Sometimes I get this exception too, and I don't know why.
-[__NSCFString sublayers]: unrecognized selector sent to instance
I tried on iOS 5 and iOS 6.
Related
I have an image above which I delimitate a transparent rectangle using layer and mask.
I would like this transparent rectangle to be red bordered. But I could find a way to achieve this :
Here is what I have done :
My ViewController has a darkenedView property.
- (void)loadView {
UIView *const rootView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
rootView.backgroundColor = [UIColor whiteColor];
self.view = rootView;
[self addContentSubviews];
}
- (void)addContentSubviews {
UIImageView *const imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"DSC_0823.jpg"]];
imageView.contentMode = UIViewContentModeScaleAspectFill;
imageView.frame = self.view.bounds;
imageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
[self.view addSubview:imageView];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self addDarkenedSubview];
}
- (void)viewDidLayoutSubviews {
CGRect const bounds = self.view.bounds;
darkenedView.center = CGPointMake(CGRectGetMidX(bounds), 0);
}
- (void)addDarkenedSubview {
darkenedView = [[UIView alloc] initWithFrame:CGRectMake(10, 30, 400, 1200)];
darkenedView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.6];
darkenedView.autoresizingMask = 0;
[self.view addSubview:darkenedView];
[self addMaskToDarkenedView];
}
- (void)addMaskToDarkenedView {
CGRect bounds = darkenedView.bounds;
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = bounds;
CGRect const myRect = CGRectMake(CGRectGetMidX(bounds) - 100,
CGRectGetMidY(bounds) + 100,
200, 200);
UIBezierPath *path = [UIBezierPath bezierPathWithRect:myRect];
[path appendPath:[UIBezierPath bezierPathWithRect:bounds]];
maskLayer.path = path.CGPath;
maskLayer.fillRule = kCAFillRuleEvenOdd;
darkenedView.layer.mask = maskLayer;
}
I've tried without success :
maskLayer.strokeColor = [UIColor redColor].CGColor;
maskLayer.lineWidth = 3.0f;
Instead of giving stroke to maskLayer object create CAShapeLayer and addSublayer to darkenedView view below is sample code Hope it help
CAShapeLayer *shape = [CAShapeLayer layer];
shape.frame = darkenedView.bounds;
shape.path = path.CGPath;
shape.lineWidth = 3.0f;
shape.strokeColor = [UIColor redColor].CGColor;
shape.fillColor = [UIColor clearColor].CGColor;
[darkenedView.layer addSublayer:shape];
I'm trying to add a shadow to a masked UIView (from a UIImage), but I believe because the view is masked to bounds, masksToBounds = YES the shadow isn't showing.
If this is indeed the case, how then can I accomplish drawing a shadow around a masked image?
-(UIView *)modifyViewWithMask:(UIView *)view
{
view.alpha = 0.5;
UIView *blackView = [[UIView alloc] initWithFrame:view.bounds];
blackView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
blackView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.3];
[view addSubview:blackView];
UIImage* bubbleImage = [UIImage imageNamed:#"Bubble"];
CALayer* mask = [CALayer layer];
mask.contents = (id)[bubbleImage CGImage];
mask.frame = CGRectMake(0, 0, view.frame.size.width, view.frame.size.height);
view.layer.mask = mask;
view.layer.masksToBounds = YES;
view.layer.shadowColor = [UIColor blackColor].CGColor;
view.layer.shadowOffset = CGSizeMake(10, 10);
view.layer.shadowRadius = 10;
view.layer.shadowOpacity = 1;
}
I have the following code:
- (void)setupLayer {
self.faucetShape = [CAShapeLayer layer];
self.faucetShape.strokeColor = [[UIColor blackColor] CGColor];
self.faucetShape.lineWidth = 2;
self.faucetShape.fillColor = nil;
self.faucetShape.path = [self faucetPath].CGPath; // faucetPath returns a 4 point diamond shaped UIBezierPath*
self.faucet = [CAGradientLayer layer];
self.faucet.mask = self.faucetShape;
self.faucet.colors = #[(id)[UIColor redColor].CGColor, (id)[UIColor redColor].CGColor];
[self.layer addSublayer: self.faucet];
}
But it shows nothing. Not sure what I'm missing (other than my graphic).
(faucet and faucetPath are both properties)
UPDATE
OK, I guess a CAGradientLayer has to have a meaningful frame. I assume a CAShapeLayer's frame isn't as meaningful, since the path can go outside the frame. Adding the following line made it show up:
self.faucet.frame = CGRectMake(0, 0, 64, 64);
So NOW my question is, is there a way to avoid having to do that? I'd just like the frame of the layer to implicitly be that of the containing View/Layer.
You need to set the frame of the layers. The following code will draw a red circle:
- (void)setupLayer
{
CAShapeLayer *faucetShape = [CAShapeLayer layer];
faucetShape.frame = CGRectMake(0, 0, 100, 100);
faucetShape.strokeColor = [[UIColor blackColor] CGColor];
faucetShape.lineWidth = 2;
faucetShape.fillColor = nil;
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddEllipseInRect(path, nil, CGRectMake(0, 0, 100, 100));
faucetShape.path = path;
CGPathRelease(path);
CAGradientLayer *faucet = [CAGradientLayer layer];
faucet.frame = CGRectMake(10, 10, 100, 100);
faucet.mask = faucetShape;
faucet.colors = #[(id)[UIColor redColor].CGColor, (id)[UIColor redColor].CGColor];
[self.view.layer addSublayer: faucet];
}
Is there any way we can make only the selected area of a superview transparent? I have a UIView with black (alpha 0.5) background color. I am putting some hollow circles on the UIView and want the underneath UIView to be transparent so that I could see through it. Any idea
- (id)initWithFrame:(CGRect)iFrame andHollowFrames:(NSArray *)iHollowFrames withRadius:(NSNumber *)iRadius {
if ((self = [super initWithFrame:iFrame]) != nil) {
self.hollowFrames = [[NSSet setWithArray:iHollowFrames] allObjects];
self.hollowCircleRadius = iRadius;
[self addShadowView];
}
return self;
}
- (void)addShadowView {
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height) cornerRadius:0];
for (NSValue *point in self.hollowFrames) {
UIBezierPath *circlePath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(point.CGPointValue.x - self.hollowCircleRadius.floatValue, point.CGPointValue.y - self.hollowCircleRadius.floatValue, 2.0 * self.hollowCircleRadius.floatValue, 2.0 * self.hollowCircleRadius.floatValue) cornerRadius:self.hollowCircleRadius.floatValue];
[path appendPath:circlePath];
}
[path setUsesEvenOddFillRule:YES];
CAShapeLayer *fillLayer = [CAShapeLayer layer];
fillLayer.path = path.CGPath;
fillLayer.fillRule = kCAFillRuleEvenOdd;
fillLayer.fillColor = [UIColor blackColor].CGColor;
fillLayer.opacity = 0.5;
[self.layer addSublayer:fillLayer];
}
Here is a UIView subclass solution. In order for this to work you must leave the superviews backgroundColor property as nil witch is the default value. If this UIView-subclass is in the storyboard then make sure you set the background color to "Clear color" in the attributes inspector.
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIColor *blackBackgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];
CGContextSetFillColorWithColor(ctx, blackBackgroundColor.CGColor);
CGContextFillRect(ctx, rect);
CGRect transparentPart = CGRectMake(10, 10, 120, 80);
CGContextClearRect(ctx, transparentPart);
}
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;