Make area around masked region transparent with CALayers? - objective-c

I'm trying to crop an image to an irregular shape, but I need to make the area that was removed transparent.
Inside subclass of UIView
CALayer *myLayer = [CALayer layer];
CAShapeLayer *mask = [CAShapeLayer layer];
myLayer.frame = self.bounds;
myLayer.contents = (id)[self.picture CGImage];
mask.path = path;
myLayer.mask = mask;
[self.layer addSublayer:myLayer];
This will crop the image appropriately, but the background color of the view is white and therefore still visible. I've tried making the other layers transparent, but it has not worked.
(self and subview both refer to same view)
[self layer].backgroundColor = [UIColor clearColor].CGColor //no change
[self layer].opacity = 0; //makes entire view transparent
subView.backgroundColor = [UIColor clearColor]; // entire view becomes transparent
Is it possible to create the effect I'm trying to achieve?

I tried the same thing, here is the exact code:
// In KMViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
CGRect frame = CGRectMake(0.0f, 0.0f, 100.0f, 100.0f);
KMView *view = [[KMView alloc] initWithFrame:frame];
view.center = self.view.center;
[self.view addSubview:view];
}
// In KMView.m
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// self.backgroundColor = [UIColor clearColor];
CALayer *layer = [CALayer layer];
CAShapeLayer *mask = [CAShapeLayer layer];
layer.frame = self.bounds;
UIImage *image = [UIImage imageNamed:#"orange"];
layer.contents = (id)image.CGImage;
CGPathRef path = CGPathCreateWithEllipseInRect(frame, NULL);
mask.path = path;
CGPathRelease(path);
layer.mask = mask;
[self.layer addSublayer:layer];
}
return self;
}
It worked for me as expected. The background was transparent. Then I tried adding
self.backgroundColor = [UIColor clearColor];
to the beginning of the code and nothing changed, the image was showing on a transparent background clipped to the path.
I think the problem might be somewhere else.

Related

how to smoothly move object on ellipse shape view

Consider this image
How would I go about drawing a custom UIView that is literally just a ellipse. Would I just override the drawRect method? And can someone show me the code for dragging red ball on ecllips path?
Drawing the ball can be done in a custom drawRect if you want, or you could use CAShapeLayer:
UIView *ball = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
ball.userInteractionEnabled = TRUE;
CAShapeLayer *ballLayer = [CAShapeLayer layer];
ballLayer.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(50, 50) radius:48 startAngle:0 endAngle:M_PI * 2.0 clockwise:YES].CGPath;
ballLayer.strokeColor = [UIColor blackColor].CGColor;
ballLayer.lineWidth = 0.5;
ballLayer.fillColor = [UIColor redColor].CGColor;
ballLayer.shadowColor = [UIColor blackColor].CGColor;
ballLayer.shadowRadius = 2;
ballLayer.shadowOpacity = 0.75;
ballLayer.shadowOffset = CGSizeZero;
[ball.layer addSublayer:ballLayer];
[self.view addSubview:ball];
Creating the ellipse could be done in a similar fashion.
Dragging the ball could be done with a gesture.
UIGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[ball addGestureRecognizer:pan];
Where:
- (void)handlePan:(UIPanGestureRecognizer *)gesture {
CGPoint translate = [gesture translationInView:gesture.view];
if (gesture.state == UIGestureRecognizerStateChanged) {
gesture.view.transform = CGAffineTransformMakeTranslation(translate.x, translate.y);
} else if (gesture.state == UIGestureRecognizerStateEnded) {
gesture.view.center = CGPointMake(gesture.view.center.x + translate.x, gesture.view.center.y + translate.y);
gesture.view.transform = CGAffineTransformIdentity;
}
}
You presumably would want to constrain the ball's movement to the ellipse, then you'd just adjust the translate coordinates accordingly. But hopefully this illustrates how to create a gesture recognizer, and move a UIView accordingly.

How to set a tableview cell's border and color when selected

I have the following code that sets sets a red border around a custom table view cell with white in the middle when it is selected.
- (void)awakeFromNib
{
self.nameLabel.highlightedTextColor = [UIColor whiteColor];
// Set selected background view
UIView *backgroundView = [[UIView alloc]initWithFrame:self.bounds];
backgroundView.layer.borderColor = [[UIColor redColor] CGColor];
backgroundView.layer.borderWidth = 10.0f;
self.selectedBackgroundView = backgroundView;
// Set the content view
CGRect frame = CGRectMake(self.bounds.origin.x+5, self.bounds.origin.y+5, self.bounds.size.width-10, self.bounds.size.height-10);
UIImageView *myImageView = [[UIImageView alloc] initWithFrame:frame];
[self addSubview:myImageView];
self.imageView.contentMode = UIViewContentModeScaleAspectFill ;
self.imageView.clipsToBounds = YES;
[self.contentView addSubview:self.imageView];
}
I want the opposite: how would you create a cell that has red with a white border / padding?
You can use
[cell.contentView.layer setBorderColor:[UIColor redColor].CGColor];
[cell.contentView.layer setBorderWidth:2.0f];
Hope this will help you
try this in custom cell there is a method - (void)setSelected:(BOOL)selected animated:(BOOL)animated use this to change the state of selected and deselect state for example
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
if(selected)
{
self.contentView.layer.cornerRadius = 10.0f;
self.contentView.layer.borderWidth = 5.0f;
self.contentView.layer.masksToBounds = YES;
self.contentView.backgroundColor = [UIColor redColor];
self.contentView.layer.borderColor = [UIColor whiteColor].CGColor;
}
else
{
self.contentView.layer.cornerRadius = 10.0f;
self.contentView.layer.borderWidth = 5.0f;
self.contentView.backgroundColor = [UIColor whiteColor];
self.contentView.layer.borderColor = [UIColor redColor].CGColor;
}
// Configure the view for the selected state
}
and also set customCell.selectionStyle = UITableViewCellSelectionStyleNone; for cell while creating the cell
why don't you try doing just the opposite such as this
UIView *backgroundView = [[UIView alloc]initWithFrame:self.bounds];
backgroundView.layer.borderColor = [[UIColor whiteColor] CGColor];
backgroundView.layer.borderWidth = 10.0f;
self.selectedBackgroundView = backgroundView;
// Set the content view
CGRect frame = CGRectMake(self.bounds.origin.x+5, self.bounds.origin.y+5, self.bounds.size.width-10, self.bounds.size.height-10);
UIImageView *myImageView = [[UIImageView alloc] initWithFrame:frame];
myImageView.backgroundcolor=[UIColor redColor];
[self addSubview:myImageView];
self.imageView.contentMode = UIViewContentModeScaleAspectFill ;
self.imageView.clipsToBounds = YES;
[self.contentView addSubview:self.imageView];

Animate Mask in Objective C

I am making an application where I have to show 3 images in a single screen, and when User touch one image then that Image should be shown by Animating and resizing.
So for 1st Step I did Masking of 3 images with 3 different Transparent Mask Image from
How to Mask an UIImageView
And my method is like below
- (UIImageView*) maskedImage:(UIImage *)image withMasked:(UIImage *)maskImage {
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
CALayer *mask1 = [CALayer layer];
mask1.contents = (id)[maskImage CGImage];
mask1.frame = CGRectMake(0, 0, 1024, 768);
imageView.layer.mask = mask1;
imageView.layer.masksToBounds = YES;
return imageView;
}
And I am calling this as
self.image2 = [UIImage imageNamed:#"screen2Full.png"];
self.mask2 = [UIImage imageNamed:#"maskImage.png"];
self.imageView2 = [self maskedImage:self.image2 withMasked:self.mask2];
[self.completeSingleView addSubview:self.imageView2];
This is working perfectly.
Now for Step 2. Animate the Mask, so that Full Image can be shown. I have googled a lot about this but didn't get success. So Please help me. I just want to know how can we Animate and Resize the Mask Image So that I can view a full Image. My concept is as below.
So finally i got the solution from multiple sites and some own logic and hard work obviously ;). So here is the solution of this
-(void)moveLayer:(CALayer*)layer to:(CGPoint)point
{
// Prepare the animation from the current position to the new position
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.fromValue = [layer valueForKey:#"position"];
animation.toValue = [NSValue valueWithCGPoint:point];
animation.duration = .3;
layer.position = point;
[layer addAnimation:animation forKey:#"position"];
}
-(void)resizeLayer:(CALayer*)layer to:(CGSize)size
{
CGRect oldBounds = layer.bounds;
CGRect newBounds = oldBounds;
newBounds.size = size;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"bounds"];
animation.fromValue = [NSValue valueWithCGRect:oldBounds];
animation.toValue = [NSValue valueWithCGRect:newBounds];
animation.duration = .3;
layer.bounds = newBounds;
[layer addAnimation:animation forKey:#"bounds"];
}
- (UIImageView*) animateMaskImage:(UIImage *)image withMask:(UIImage *)maskImage width:(int )wd andHeight:(int )ht andSize:(CGSize)size andPosition:(CGPoint)pt {
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
CALayer *mask = [CALayer layer];
mask.contents = (id)[maskImage CGImage];
mask.frame = CGRectMake(0, 0, wd, ht);
//This needs to be place before masking. I don't knwo why. But its working :)
[self resizeLayer:mask to:size];
[self moveLayer:mask to:pt];
imageView.layer.mask = mask;
imageView.layer.masksToBounds = YES;
return imageView;
}
You just need to call this as
[self.imageView1 removeFromSuperview];
self.imageView1 = [self animateMaskedImage:self.image1 withMasked:self.mask1 width:1024 andHeight:768 andSize:CGSizeMake(1024*4, 768*4) andPosition:CGPointMake(1024*2, 768*2)];
[self.completeSingleView addSubview:self.imageView1];

iOS: Table cell footer dropshadow

I have a tableview with only a few rows. So instead of displaying a bunch of blanks I added a blank UIView to the tableview.footer. However I would like the last cell to cast a dropshadow on the UIView. How would I achieve this? Here is my current code.
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *emptyView = [[UIView alloc] initWithFrame:CGRectZero];
CALayer *layer = [emptyView layer];
[layer setShadowOffset:CGSizeMake(0, 1)];
[layer setShadowColor:[[UIColor darkGrayColor] CGColor]];
[layer setShadowRadius:8.0];
[layer setShadowOpacity:0.8];
self.tableView.tableFooterView = emptyView;
}
EDIT:
It is adding the UIView to the footer but not creating the dropshadow. I'm not sure the layer is the best approach for this or even correct for this type of thing.
It is probably because you set the frame to CGRectZero.
The zero rectangle is equivalent to CGRectMake(0,0,0,0).
A shadow of a zero rect (x=0, y=0, width=0, height=0) won't show at all.
Try giving it a proper frame size and you will see the difference.
Check out this for ref as well: https://developer.apple.com/library/mac/#documentation/graphicsimaging/reference/CGGeometry/Reference/reference.html
I ended up using
shadowBackgroundView.layer.shadowOpacity = 0.3;
shadowBackgroundView.layer.shadowRadius = 2;
shadowBackgroundView.layer.shadowColor = [[UIColor blackColor] CGColor];
shadowBackgroundView.layer.shadowOffset = CGSizeMake(0.0, 1.0);
CGPathRef shadowPath = [UIBezierPath bezierPathWithRoundedRect: shadowBackgroundView.bounds
byRoundingCorners: UIRectCornerAllCorners
cornerRadii: CGSizeMake(PageCellBackgroundRadius, PageCellBackgroundRadius)].CGPath;
shadowBackgroundView.layer.shadowPath = shadowPath;
shadowBackgroundView.layer.shouldRasterize = YES;
[self addSubview: shadowBackgroundView];
In cellForRowAtIndexPath: function:
// drop shadow
cell.layer.shadowOpacity = 1.0;
cell.layer.shadowRadius = 1.7;
cell.layer.shadowColor = [UIColor blackColor].CGColor;
cell.layer.shadowOffset = CGSizeMake(0.0, 0.0);

UIImage/UIImageView redraw when containing UIView is scaled

My iPad app has a navigation where I show screenshots of the different pages and because I want to show more than one screenshot at once I scale the container to around 24% of the original screenshots (1024x768).
- (void) loadView
{
// get landscape screen frame
CGRect screenFrame = [UIScreen mainScreen].bounds;
CGRect landscapeFrame = CGRectMake(0, 0, screenFrame.size.height, screenFrame.size.width);
UIView *view = [[UIView alloc] initWithFrame:landscapeFrame];
view.backgroundColor = [UIColor grayColor];
self.view = view;
// add container view for 2 images
CGRect startFrame = CGRectMake(-landscapeFrame.size.width/2, 0, landscapeFrame.size.width*2, landscapeFrame.size.height);
container = [[UIView alloc] initWithFrame:startFrame];
container.backgroundColor = [UIColor whiteColor];
// add image 1 (1024x768)
UIImage *img1 = [UIImage imageNamed:#"01.jpeg"];
UIImageView *img1View = [[UIImageView alloc] initWithImage:img1];
[container addSubview:img1View];
// add image 2 (1024x768)
UIImage *img2 = [UIImage imageNamed:#"02.jpeg"];
UIImageView *img2View = [[UIImageView alloc] initWithImage:img2];
// move img2 to the right of img1
CGRect newFrame = img2View.frame;
newFrame.origin.x = 1024.0;
img2View.frame = newFrame;
[container addSubview:img2View];
// scale to 24%
container.transform = CGAffineTransformMakeScale(0.24, 0.24);
[self.view addSubview:container];
}
but when I scale images with "small" text it looks sth like this:
I have to use the big screenshots because if a user taps the image it should scale to 100% and be crispy clear.
is there a way how I can scale the images "smoothly" (on the fly) without ruining performance?
it would be enough to have two versions: the full-px one and another for the 24% version.
The reason the scaled-down image looks crappy is it's being scaled in OpenGL, which is using fast-but-low-quality linear interpolation. As you probably know, UIView is built on top of CALayer, which is in turn a sort of wrapper for OpenGL textures. Because the contents of the layer reside in the video card, CALayer can do all of its magic on the GPU, independent of whether the CPU is busy loading a web site, blocked on disk access, or whatever. I mention this only because it's useful to pay attention to what's actually in the textures inside your layers. In your case, the UIImageView's layer has the full 1024x768 bitmap image on its texture, and that isn't affected by the container's transform: The CALayer inside the UIImageView doesn't see that it's going to be (let's see..) 246x185 on-screen and re-scale its bitmap, it just lets OpenGL do its thing and scale down the bitmap every time it updates the display.
To get better scaling, we'll need to do it in CoreGraphics instead of OpenGL. Here's one way to do it:
- (UIImage*)scaleImage:(UIImage*)image by:(float)scale
{
CGSize size = CGSizeMake(image.size.width * scale, image.size.height * scale);
UIGraphicsBeginImageContextWithOptions(size, YES, 0.0);
[image drawInRect:CGRectMake(0, 0, size.width, size.height)];
UIImage *imageCopy = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return imageCopy;
}
- (void)loadView
{
// get landscape screen frame
CGRect screenFrame = [UIScreen mainScreen].bounds;
CGRect landscapeFrame = CGRectMake(0, 0, screenFrame.size.height, screenFrame.size.width);
UIView *view = [[UIView alloc] initWithFrame:landscapeFrame];
view.backgroundColor = [UIColor grayColor];
self.view = view;
// add container view for 2 images
CGRect startFrame = CGRectMake(-landscapeFrame.size.width/2, 0, landscapeFrame.size.width*2, landscapeFrame.size.height);
container = [[UIView alloc] initWithFrame:startFrame];
container.backgroundColor = [UIColor whiteColor];
// add image 1 (1024x768)
UIImage *img1 = [UIImage imageNamed:#"01.png"];
img1View = [[TapImageView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)];
img1View.userInteractionEnabled = YES; // important!
img1View.image = [self scaleImage:img1 by:0.24];
[container addSubview:img1View];
// add image 2 (1024x768)
UIImage *img2 = [UIImage imageNamed:#"02.png"];
img2View = [[TapImageView alloc] initWithFrame:CGRectMake(1024, 0, 1024, 768)];
img2View.userInteractionEnabled = YES;
img2View.image = [self scaleImage:img2 by:0.24];
[container addSubview:img2View];
// scale to 24% and layout subviews
zoomed = YES;
container.transform = CGAffineTransformMakeScale(0.24, 0.24);
[self.view addSubview:container];
}
- (void)viewTapped:(id)sender
{
zoomed = !zoomed;
[UIView animateWithDuration:0.5 animations:^
{
if ( zoomed )
{
container.transform = CGAffineTransformMakeScale(0.24, 0.24);
}
else
{
img1View.image = [UIImage imageNamed:#"01.png"];
img2View.image = [UIImage imageNamed:#"02.png"];
container.transform = CGAffineTransformMakeScale(1.0, 1.0);
}
}
completion:^(BOOL finished)
{
if ( zoomed )
{
UIImage *img1 = [UIImage imageNamed:#"01.png"];
img1View.image = [self scaleImage:img1 by:0.24];
UIImage *img2 = [UIImage imageNamed:#"02.png"];
img2View.image = [self scaleImage:img2 by:0.24];
}
}];
}
And here's TapImageView, a UIImageView subclass that tells us when it's been tapped by sending an action up the responder chain:
#interface TapImageView : UIImageView
#end
#implementation TapImageView
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
[[UIApplication sharedApplication] sendAction:#selector(viewTapped:) to:nil from:self forEvent:event];
}
#end
Instead of scaling the container and all of its subviews. Create a UIImageView from the contents of the container and adjust its frame size to 24% of the original.
UIGraphicsBeginImageContext(container.bounds.size);
[container renderInContext:UIGraphicsGetCurrentContext()];
UIImage *containerImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *containerImageView = [[UIImageView alloc] initWithImage:containerImage];
CGRectFrame containerFrame = startFrame;
containerFrame.size.with *= 0.24;
containerFrame.size.height *= 0.24;
containerImageView.frame = containerFrame;
[self.view addSubView:containerImageView];