I have an image view that starts out cropping it's image with clipsToBounds and content mode set as "scale aspect fill", and I want it to "enlarge" the image to the whole image. If clipsToBounds=NO was an animatable property, that would be exactly what I want, which it does not seem to be. Is there a way to animate that?
If not, another way would be resizing the view so it is the same width-height ratio as the image's size, while keeping it no smaller than the image view was to begin with (i.e. minimal increate to height or width, no decrease to either). I'm not sure the best approach to doing this, considering the image could be much bigger or smaller than the image view, and the image's width-height ratio could be just about anything (but it will usually be an iOS device camera photo).
UPDATE 1: It seems like layer.masksToBounds would work, the documentation says that it is animatable, but my code does not seem to work:
CABasicAnimation *layerAnim = [CABasicAnimation animationWithKeyPath:#"masksToBounds"];
layerAnim.fromValue = #(YES);
layerAnim.toValue = #(NO);
layerAnim.removedOnCompletion = YES;
layerAnim.duration = 1.0;
[_imageView.layer addAnimation:layerAnim forKey:#"masksToBounds"];
I am running this layer animation at the same time as a UIView block animation that is changing the frame and transform of the image view, if that matters.
UPDATE 2: I did not have this core animation in the UIView animation block, which according to the documentation, it should be. I have moved the above code into the animation block, but it is still not animating (change happens instantly). I'm beginning to think that "animatable" simply means it can be placed in an animation, not that it will actively animate over time.
So you don't want your image to grow, but you want it to be clipped at first, and then the outer pixels are exposed?
You can do that using Core Animation and a layer mask.
Here's what you do:
Set the image view's clipsToBounds to FALSE, so the image would fully display if you let it.
Create a CAShapeLayer that's the size of the whole image. Create a rectangular bezier path that's the size of the initial image. Install that bezier path's CGPath as the path of the shape layer.
Set the shape layer's fill color to an opaque color
Then install the shape layer as the mask of your image view's layer. That will cause the image to be clipped to the shape of the shape layer.
Now, if you change the path that's installed in the shape layer to a rectangle that's the full size of the image, the system will animate the change for you.
As of March 21st 2019, it seems like the masksToBounds documentation is just wrong. masksToBounds does not seem to be animatable. I have tried animating masksToBounds with CABasicAnimation and CAKeyframeAnimations and it doesn't seem to work.
Related
I am having some difficulty understanding on how layer masking works. Right now, I have a UIView with UILabels on it. I picture two layers - one with the UIView in the back and one for the labels on top. If I mask the UIView layer, the labels will be affected by the mask too.
The UILabels are children of the parent UIView, so I can understand a parent mask affecting the children as well.
However, when I look at it in terms of layers, it doesn't seem to make sense. Why does masking the deepest layer affect those on top?
Think of layers as sheets of paper. Think of the view's layer as a big sheet of white paper. As you figured out, the labels' layers are children of the view's layers. To relate, think of the labels' layers being strips of paper glued onto the big view's layer-sheet.
Let's say you wish to mask the layer with a circle. To translate that into our little analogy, you wish to cover the big view's layer-sheet with Harry Potter's invisibility cloak, with a circle shaped hole in it.
To do that, you'd cut the invisibility cloak to the same size as that of your view's layer-sheet.
cloakLayer.frame = bigViewLayer.frame;
Then, you'd carefully cut out a circle from it.
cloakLayer.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(0, 0) radius:15.0 startAngle:0.0 endAngle:2 * M_PI clockwise:YES];
cloakLayer.fillColor = [UIColor blackColor].CGColor; // the hole
Then, you'd paste this cloak-with-a-hole onto your big view's layer-sheet, carefully aligning the edges.
bigViewLayer.mask = cloakLayer;
What's gonna go invisible? Anything on the sheet (because the cloak was cut to the sheet's dimensions) that doesn't fall into the circle you removed from the cloak. That's the mask property.
Let's talk about the masksToBounds property.
Let's say while pasting the strips of label layer-sheets onto the big view's layer-sheet, you decided to place only half of the strip on the sheet, and made the rest hang off the edge(s).
Let's say you set masksToBounds to YES. What the gods of decoupage would do now is neatly cut off the parts of your labels' strips that are not within the edges of the big view's layer-sheet. That's the masksToBounds property.
Let's talk about borders. This is simple. Just pick a sharpie of borderColor whose nib is borderWidth points wide, and carefully draw on the edges of the view's layer-sheet. That's it.
I hope you get things now and can make your own analogies for other properties of the wonderful CALayer.
As you said, the UIView is the parent, and the UILabels are the children. When it comes time to update the screen, the UIView starts with a blank canvas. It draws itself into the canvas, and then has the children draw themselves into the canvas. When the children are drawing, they are subject to constraints imposed by the parent, e.g. clipping and masking.
iOS CALayer.mask
[CALayer]
[iOS CALayer.masksToBounds]
CALayer has a mask property which is CALayer, which applies mask's alpha channel to mask parent's layer.
layer + mask = masked layer
I'm not sure if this has been asked before, but I'm having a hard time finding it. Perhaps I'm not using the right search terms, so if an answer already exists, if someone could point me in the right direction, it'd be most appreciated!
I just noticed that the glimmer animation on the "slide to unlock" text of the lockscreen has changed with the iOS 7.1 update. The spotlight now has an ovular / diamond shape that cascades across the letters without appearing on the view behind it.
In the past, I've replicated this type of feature by changing the color of individual letters sequentially, but for this, the animation goes through the middle of the letters. Without affecting the background.
How can I replicate this?
You can animate label text and use custom slider for it, I hope it helps you:
CALayer *maskLayer = [CALayer layer];
// Mask image ends with 0.15 opacity on both sides. Set the background color of the layer
// to the same value so the layer can extend the mask image.
maskLayer.backgroundColor = [[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.15f] CGColor];
maskLayer.contents = (id)[[UIImage imageNamed:#"Mask.png"] CGImage];
// Center the mask image on twice the width of the text layer, so it starts to the left
// of the text layer and moves to its right when we translate it by width.
maskLayer.contentsGravity = kCAGravityCenter;
maskLayer.frame = CGRectMake(myLabel.frame.size.width * -1, 0.0f, myLabel.frame.size.width * 2, myLabel.frame.size.height);
// Animate the mask layer's horizontal position
CABasicAnimation *maskAnim = [CABasicAnimation animationWithKeyPath:#"position.x"];
maskAnim.byValue = [NSNumber numberWithFloat:myLabel.frame.size.width];
maskAnim.repeatCount = 1e100f;
maskAnim.duration = 1.5f;
[maskLayer addAnimation:maskAnim forKey:#"slideAnim"];
myLabel.layer.mask = maskLayer;
You should be able to use the mask property of CALayer to create a cutout of the contents of another layer.
Set the mask to contain your text (maybe a CATextLayer can work here). This is what Shimmer says it uses.
Make the foreground color of your label be a UIColor initiated with
+colorWithPatternImage or
-initWithPatternImage
using an animated image and setting the background color of the label to transparent. I've not tried this, but I don't see why it wouldn't work.
The best way to do this is with a multi layer object.
Top: UILabel with opaque background and clear text
Clear text is rendered in drawRect: func through complicated masking process
Middle: Worker View that is performing a repeating animation moving an image behind the top label
Bottom: a UIView that you add the middle and top subview to in that order. Can be whatever color you want the text to be
An example can be seen here
https://github.com/jhurray/AnimatedLabelExample
The most effective way I've found to recreate the glimmering text effect is to use the Shimmer Cocoapod created by Facebook. Below is the example image from the Shimmer GitHub repo, which is located at the following URL: https://github.com/facebook/Shimmer
Shimmer example
There are full instructions to install and use Shimmer on the repo, but the gist is that after installing the Cocoapod you'll add a special subview or layer into which will go the contents you wish to have glimmer/shimmer, then set the effect to start.
Try to have a semi-transparent foreground with transparent cutouts for the letters. The "glimmer" can be moved across behind the cutouts.
Make a layer on top that has cutout layers with an animated PNG or something as the background.
Under this layer, have another layer with exactly the reverse transparency (letters are opaque and space between letters is transparent.
This way, the user sees through the letters to the animation, and between the letters to whatever the letters are over.
Just make sure you have code to keep the layers in the right order.
I think that it's a semi transparent view, but it's a special view in which the drawrect is overridden to color each pixel of the letters with the same color (but stronger to make it visible) of the pixel in the view beneath it.
Imagine this like the magnifying view. it displays a magnified version of the the view beneath it.
My problem is:
I have a CAShapeLayer named outCircle. I add some CATextLayers as sublayers of outCircle. The problem is that when I'm pinching outCircle and doing so scale it, the sublayer CATextLayers resize too (maybe obvious) and become blurry and not crisp. The text looks horrible!
So I want to keep the original size and (maybe) position of the text layers yet keeping them sublayers of outCircle.
Is it possible?
How can I do it?
One solution is to not make the CATextLayers sublayers of outCircle but for various reasons I need to do it. Thanks for your help in advice!
try setting the inverse-scale value to textLayers.
i.e if you set scale of .5 to shapeLayer, set (1/.5 =2) as scale to textLayers.
shapeLayer.affineTransform =CGAffineTransformMakeScale(.5, .5);
textLayer.affineTransform =CGAffineTransformMakeScale(2, 2);
I am rotating a view by some angle by after rotating I am not getting the smooth edge of the view. Please check the attached image. Is there any way to have a smooth edge of UIView?
I have used the following statement for this purpose.
CGContextRotateCTM(currentContext, RADIANS(-5));
It helps a lot if you set shouldRasterize = true for the layer of your view.
yourView.layer.shouldRasterize = true
When the value of this property is true, the layer is rendered as a
bitmap in its local coordinate space and then composited to the
destination with any other content. Shadow effects and any filters in
the filters property are rasterized and included in the bitmap.
However, the current opacity of the layer is not rasterized. If the
rasterized bitmap requires scaling during compositing, the filters in
the minificationFilter and magnificationFilter properties are applied
as needed.
Try enabling anti-aliasing before rotating:
CGContextSetShouldAntialias(currentContext, true);
set 1 pixel transparent border. This will helps you magically.
I have a requirement where I have to draw a view's shadow beyond the view's frame. We already have complex view hierarchy / layering and it would not be possible now to change the view's frames and layout to accommodate the shadows within the viewController's view's bounds.
1. I am bit concerned here of whether I am allowed to draw the shadow's outside the view by following this methodology:
"You can also create a drop shadow that will be based on the alpha component of whatever is drawn in your view. Often this will result in a shadow just around the edges of the view. This example code on a UILabel:
label.layer.shadowColor = [UIColor blackColor].CGColor;
label.layer.shadowOpacity = 1.0;
label.layer.shadowRadius = 5.0;
label.layer.shadowOffset = CGSizeMake(0, 3);
label.clipsToBounds = NO;
In this case, you need clipsToBounds to be NO in order for a shadow outside the frame of your view to show up. Next I’ll show you how you can actually combine rounded corners and drop shadows, since I’m sure that’s what you really want to do now."
Reference: http://bynomial.com/blog/?p=52
2. Now, I have come across threads which tells that drawing shadows outside the view is not encouraged. But no explanation given:
How can I draw a shadow beyond a UIView's bounds?
Is there any reason as why we should not be drawing shadows (CALayer) outside view's frame? Or, is it fine following the 1st approach?
Thanks,
Raj
I see absolutely no reason not to draw a shadow this way, and this is frankly the only way. Why would you "not be allowed" to draw a shadow outside the bounds? That's what the clipsToBounds property is for.