iOS scaling with CGAffineTransformMakeScale on drawRect content - ios

float scaleFactor = 0.5;
self.bubble.transform = CGAffineTransformMakeScale(scaleFactor, scaleFactor);
[self.bubble setNeedsDisplay];
[UIView animateWithDuration:2.0 animations:^{
self.frame = _rectToMoveTo;
} completion:^(BOOL finished) {
[UIView animateWithDuration:2.0f animations:^{
self.bubble.transform = CGAffineTransformMakeScale(1.0, 1.0);
}];
}];
The above code performs the animation mostly correct. As you can see, the self.bubbles are scaled down to ½ then animated back to normal. The problem is that self.bubble has a drawRect method for drawing a circle. The problem is that this circle gets scaled down to ¼ from the start! When the second animation runs, the subviews of self.bubble get scaled to normal, but the circle scales up to only ½. I've tried using setNeedsDisplay to redraw the circle but it will only work after the animations have completed so it's no good. how do you fix this?
Edit: here is _bubble drawRect method.
CGContextRef c = UIGraphicsGetCurrentContext();
if (!_colour) _colour = [UIColor darkGrayColor];
[_colour setFill];
[[UIColor clearColor] setStroke];
CGContextAddEllipseInRect(c, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height));
CGContextFillPath(c);
[self addSubview:_title];
[self addSubview:_icon];

self.frame is in the superview's coordinate system, and is thus affected by your transform, which you don't want.
Inside drawRect:, you should only use self.bounds, not self.frame.
Also, you should not add subviews in drawRect:.

Related

Move image 90 degree using Bezier curve

I am new to iOS please some one help me to move image using Bezier curve along 90 degree path. I had searched but my doubt is not clear yet, So please help me.
I had tried this code but I would like to use Bezier curve.
- (void)startApp {
UIImageView *imageToMove =
[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"man.jpg"]];
imageToMove.frame = CGRectMake(10, 10, 100, 100);
[self.view addSubview:imageToMove];
// Move the image
[self moveImage:imageToMove
duration:1.0
curve:UIViewAnimationCurveLinear
x:70.0
y:70.0];
}
- (void)moveImage:(UIImageView *)image
duration:(NSTimeInterval)duration
curve:(int)curve
x:(CGFloat)x
y:(CGFloat)y {
// Setup the animation
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:duration];
[UIView setAnimationCurve:curve];
[UIView setAnimationBeginsFromCurrentState:YES];
// The transform matrix
CGAffineTransform transform = CGAffineTransformMakeTranslation(x, y);
image.transform = transform;
// Commit the changes
[UIView commitAnimations];
}
You can rotate (transform) view using CABasicAnimation also. I have done some calculation for that.
- (void) rotateAnimation {
CABasicAnimation *topAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
topAnimation.autoreverses = NO;
topAnimation.duration = 0.8;
topAnimation.repeatDuration = 0;
topAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DPerspect(CATransform3DMakeRotation(M_PI, 1, 0, 0), CGPointMake(0, 0), 400)];
[image.layer addAnimation:topAnimation forKey:nil];
}
And supporting methods for that rotation are below:
CATransform3D CATransform3DMakePerspective(CGPoint center, float disZ)
{
CATransform3D transToCenter = CATransform3DMakeTranslation(-center.x, -center.y, 0);
CATransform3D transBack = CATransform3DMakeTranslation(center.x, center.y, 0);
CATransform3D scale = CATransform3DIdentity;
scale.m34 = -1.0f/disZ;
return CATransform3DConcat(CATransform3DConcat(transToCenter, scale), transBack);
}
CATransform3D CATransform3DPerspect(CATransform3D t, CGPoint center, float disZ)
{
return CATransform3DConcat(t, CATransform3DMakePerspective(center, disZ));
}
About rotation is on X-axis (horizontal) on respective layer. You can change axis value to Y and Z from below line in rotateAnimation.
CATransform3DMakeRotation(M_PI, 1, 0, 0), CGPointMake(0, 0), 400)]
By changing 1, 0, 0 values to 0, 1, 0 in above code will change rotation on Y axis and similarly for Z axis. Replace values and have look for rotation you like.
Here is my UIImageView with IBOutlet named drawPad.
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(150, 150)
radius:100
startAngle:0
endAngle:(22.0f/7.0f)
clockwise:YES];
UIGraphicsBeginImageContext(self.view.frame.size);
path.lineCapStyle = kCGLineCapRound;
path.lineWidth = 10.0f;
[[UIColor blackColor] setStroke];
[path stroke];
self.drawpad.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

View shrink ( not fade out ) using core animation

I have subclassed a UIButton to make the button shape appear as a doughnut ( circle with a transparent hole in the middle )
The drawRect is :
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
if(context==nil)
context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:0 green:1.0 blue:0 alpha:0.5].CGColor);
CGMutablePathRef path = CGPathCreateMutable();
CGContextSetLineWidth(context, self.frame.size.width/2 - innerRadius);
CGPathAddArc(path, NULL, self.frame.size.width/2.0, self.frame.size.height/2.0, (innerRadius+((self.frame.size.width/2 - innerRadius)/2.0)), 0 * 3.142/180, 2.1 *3.14, 0);
CGContextAddPath(context, path);
CGContextStrokePath(context);
CGPathRelease(path);
}
When the button is clicked, I want the inner hole to shrink gradually and finally disappear leaving the circle whole .
innerRadius controls the size of the inner hole so I used the following function to try to achieve what I want :
- (void) animateButton
{
innerRadius = 0;
[UIView transitionWithView:self duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
[self.layer displayIfNeeded];
} completion:nil];
}
This works but not quite like I wanted. The hole just fades out over a 0.5 second period. I want it to gradually shrink and then disappear instead. How can I achieve this using core animation ?

Capturing (animated) screen behind UIView

I asked a similar question recently on how to add a translucent effect to a UIView and got a good response.
However it used a lot of CPU power to process so I used some of the ideas behind the answer but did the filtering using GPUImage which is much more efficient.
It works well for a static screen, but I want to change the background UIImageView's image with an animation. However when I set the UIView to sample the background during the transition, it seems to ignore the transition and show the new Image before the animation has started.
The relevant code is as follows (ask if you need more!):
The superview containing the custom UIView and the UIImageView background:
//The Transition
[contentView setScheduled:YES]; //Starts the sampling every 0.2 seconds
//bg is the UIImageView and contentView is the 'translucent' UIView subclass
[UIView transitionWithView:bg duration:1.0 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
[bg setImage:newImage];
}completion:^(BOOL finished){
[contentView setScheduled:NO];
}];
Then in the UIView subclass:
- (UIImage *)snapshotOfSuperview:(UIView *)superview
{
CGFloat scale = 0.5;
if (([UIScreen mainScreen].scale > 1 || self.contentMode == UIViewContentModeScaleAspectFill))
{
CGFloat blockSize = 12.0f/5;
scale = blockSize/MAX(blockSize * 2, floor(self.blurRadius));
}
UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, -self.frame.origin.x, -self.frame.origin.y);
self.hidden=YES; //Don't take a snapshot of the view
[superview.layer renderInContext:context];
self.hidden=NO;
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snapshot;
}
-(void)updateViewBG{
UIImage *superviewImage = [self snapshotOfSuperview:self.superview];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
GPUImageGaussianBlurFilter* filter = [[GPUImageGaussianBlurFilter alloc] init];
filter.blurSize = 0.8f;
UIImage* newBG = [self applyTint:self.tintColour image:[filter imageByFilteringImage:superviewImage]];
dispatch_async(dispatch_get_main_queue(), ^{
self.layer.contents = (id)newBG.CGImage;
self.layer.contentsScale = newBG.scale;
});
});
}
-(UIImage*)applyTint:(UIColor*)colour image:(UIImage*)inImage{
UIImage *newImage;
if (colour) {
UIGraphicsBeginImageContext(inImage.size);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect area = CGRectMake(0, 0, inImage.size.width, inImage.size.height);
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -area.size.height);
CGContextSaveGState(ctx);
CGContextClipToMask(ctx, area, inImage.CGImage);
[colour set];
CGContextFillRect(ctx, area);
CGContextRestoreGState(ctx);
CGContextSetBlendMode(ctx, kCGBlendModeLighten);
CGContextDrawImage(ctx, area, inImage.CGImage);
newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}else{
newImage = inImage;
}
return newImage;
}
-(void)displayLayer:(CALayer *)layer{
[self updateViewBG];
}
How can I get the background to follow the animation?
I think the problem is that as soon as you change the image, displayLayer is called (disregarding the transition being in progress) and thus the new image is used for the translucency.
You should modify updateViewBG so that it does not update the layer content before the transition is finished. E.g., you could add a flag to your class, set it when you start the transition and reset it when it completes. When the flag is set you do not call updateViewBG.
[UIView transitionWithView:bg duration:1.0 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
self.isTransitionInProgress = YES;
[bg setImage:newImage];
}completion:^(BOOL finished){
[contentView setScheduled:NO];
self.isTransitionInProgress = NO;
[contentView.layer setNeedsDisplay];
}];
-(void)displayLayer:(CALayer *)layer{
if (!self.isTransitionInProgress)
[self updateViewBG];
}

Animating the mask for a UIView

I can create a mask like this:
CALayer *mask = [CALayer layer];
mask.contents = (id)[[UIImage imageNamed:#"mask.png"] CGImage];
mask.frame = CGRectMake(0, 0, 10, 10);
self.content.layer.mask = mask;
And this will correctly reveal the top left 10 pixels of my content (because mask.png is just a black image). However I want to animate the mask to reveal the rest of the content:
[UIView animateWithDuration:3.0
animations:^{
mask.frame = self.content.bounds;
}
completion:^(BOOL finished){
}];
The problem is that there's no animation. The entire content gets displayed immediately. Why does this happen, and how can I animate the mask so that the content is revealed from the upper left?
The frame is a derived property of various other properties, such as the position, bounds, anchorPoint, and any transform it has applied to it. It's not recommended to animate this property directly, especially when dealing with lower level CoreAnimation layers.
In this case, I would assume you want to animate the bounds. You can use the UIView animation method above, but when dealing with CALayers directly I prefer to use the CoreAnimation methods of animation.
CGRect oldBounds = mask.bounds;
CGRect newBounds = self.content.bounds;
CABasicAnimation* revealAnimation = [CABasicAnimation animationWithKeyPath:#"bounds"];
revealAnimation.fromValue = [NSValue valueWithCGRect:oldBounds];
revealAnimation.toValue = [NSValue valueWithCGRect:newBounds];
revealAnimation.duration = 3.0;
// Update the bounds so the layer doesn't snap back when the animation completes.
mask.bounds = newBounds;
[mask addAnimation:revealAnimation forKey:#"revealAnimation"];

CALayer animates with frame change?

I have a CALayer I've added to my view:
myView.myCALayer = [[CALayer alloc] init];
CGSize size = myView.frame.size;
myView.myCALayer.frame = CGRectMake(0, 0, size.width, size.height);
myView.myCALayer.backgroundColor = [[UIColor blackColor] CGColor];
[myView.layer addSublayer:myView.myCALayer];
When I attempt to change the frame of the CALayer after changing the frame of myView, the resize of the CALayer animates. I have added no animation to the CALayer, so I don't understand this. I've even attempted to call removeAllAnimations on the layer before the resize and it still animates the resize.
Anyone know what could be going on here?
There is actually an implicit animation on setting some values for a CALayer. You have to disable the animations before you set a new frame.
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
[myView.myCALayer.frame = (CGRect){ { 10, 10 }, { 100, 100 } ];
[CATransaction commit];
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreAnimation_guide/AdvancedAnimationTricks/AdvancedAnimationTricks.html#//apple_ref/doc/uid/TP40004514-CH8-SW3
In Swift 4:
CATransaction.begin()
CATransaction.setDisableActions(true)
/* Your layer / frame changes here */
CATransaction.commit()

Resources