MBProgressHUD with CustomView: Why can't I animate my custom view? - objective-c

This somehow doesn't work...why? How can I achieve a spinning custom propeller without making a gif out of the animation?
-(UIView *)propTest
{
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 37, 37)];
UIImageView *movablePropeller = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 37 , 37)];
movablePropeller.image = [UIImage imageNamed:#"MovablePropeller"];
[view addSubview:movablePropeller];
movablePropeller.center = view.center;
CABasicAnimation *rotation;
rotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotation.fromValue = [NSNumber numberWithFloat:0.0f];
rotation.toValue = [NSNumber numberWithFloat:(2 * M_PI)];
rotation.cumulative = true;
rotation.duration = 1.2f; // Speed
rotation.repeatCount = INFINITY; // Repeat forever. Can be a finite number.
[movablePropeller.layer addAnimation:rotation forKey:#"Spin"];
return view;
}
-(void)presentMyHud
{
MBProgressHUD *hud = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:hud];
hud.mode = MBProgressHUDModeCustomView;
hud.customView = [self propTest];
hud.detailsLabelText = #"Getting data";
[hud show:YES];
}
But my propeller stays static...

If the propeller is not spinning, that can happen if you didn’t immediately add this view to the view hierarchy. Generally, it’s prudent to add the view to the view hierarchy before you addAnimation.
- (UIView *)addPropellerToView:(UIView *)view {
UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 37, 37)];
// make sure to add it to the view hierarchy
[view addSubview:containerView];
// if you want to center it, set its `center` to be in the middle of the `bounds` ... don't use `center`
containerView.center = CGPointMake(view.bounds.origin.x + view.bounds.size.width / 2, view.bounds.origin.y + view.bounds.size.height / 2);
// I'd just use the `bounds` of the container view; reducing the risk that we get its coordinates wrong
UIImageView *movablePropeller = [[UIImageView alloc] initWithFrame:containerView.bounds];
movablePropeller.image = [UIImage imageNamed:#"MovablePropeller"];
movablePropeller.contentMode = UIViewContentModeScaleAspectFill;
[containerView addSubview:movablePropeller];
// never set the center of a view to the center of its super view ... those are two different coordinate systems.
// in this case, this line isn't needed at all, so I'll remove it.
//
// movablePropeller.center = containerView.center;
CABasicAnimation *rotation;
rotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotation.fromValue = #(0);
rotation.toValue = #(2 * M_PI);
rotation.cumulative = true;
rotation.duration = 1.2f;
rotation.repeatCount = INFINITY;
[movablePropeller.layer addAnimation:rotation forKey:#"Spin"];
return containerView;
}
Yielding:
Some unrelated observations:
If you want to center view A in the middle of view B, set view A’s center to coordinates to the midpoint of B’s bounds, not to B’s center. E.g., you never want to do:
A.center = B.center;
What you want is:
A.center = CGPointMake(B.bounds.origin.x + B.bounds.size.width / 2, B.bounds.origin.y + B.bounds.size.height / 2);
I know it looks like it should be the same thing, but it’s not. A’s center is defined, like frame, in the coordinate system of B. But B’s center is defined in the coordinate system of its superview, which might be completely different. Sometimes you won’t notice the difference (specifically if B’s origin is {0, 0}), but it suggests a misunderstanding of the different coordinate systems, and if B isn’t at {0, 0}, then everything will be wrong.
You can use NSNumber literals, replacing [NSNumber numberWithFloat:0.0f] with #(0).
You really don’t need that container view, so you could simplify the routine, like below.
- (UIView *)addPropellerTo:(UIView *)view {
UIImageView *movablePropeller = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 37, 37)];
movablePropeller.image = [UIImage imageNamed:#"MovablePropeller"];
movablePropeller.contentMode = UIViewContentModeScaleAspectFill;
[view addSubview:movablePropeller];
movablePropeller.center = CGPointMake(view.bounds.origin.x + view.bounds.size.width / 2, view.bounds.origin.y + view.bounds.size.height / 2);
CABasicAnimation *rotation;
rotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotation.fromValue = #(0);
rotation.toValue = #(2 * M_PI);
rotation.cumulative = true;
rotation.duration = 1.2f;
rotation.repeatCount = INFINITY;
[movablePropeller.layer addAnimation:rotation forKey:#"Spin"];
return movablePropeller;
}

Related

How to use an additive animation on AVPlayerLayer bounds?

I'm trying to do an additive animation on a AVPlayerLayer's bounds. By simply setting additive to YES, it breaks the animation. I need to do it additive so that the width and height can be animated with different animation parameters. Animating the key paths bounds.size.width and bounds.size.height do not work either.
I can animate other CALayer classes fine with the below code, so I'm starting to wonder if AVPlayerLayer has a bug.
Here is the code:
AVPlayerLayer *vl;
// ...
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSURL *path = [[NSBundle mainBundle] URLForResource:#"vid" withExtension:#"mp4"]];
AVPlayer *player = [AVPlayer playerWithURL: path];
vl = [AVPlayerLayer playerLayerWithPlayer:player];
vl.videoGravity = AVLayerVideoGravityResize;
// vl = [CALayer layer]; // a normal calayer animates properly
vl.backgroundColor = [NSColor redColor].CGColor;
vl.frame = CGRectMake(200,100,150,100);
[_window.contentView.layer addSublayer:vl];
}
- (IBAction)animate:(id)sender {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"bounds"];
animation.fromValue = [NSValue valueWithRect:CGRectMake(0,0,0, 0)];
animation.toValue = [NSValue valueWithRect:CGRectMake(0,0,240, 60)];
animation.duration = 4;
animation.additive = YES;
[vl addAnimation:animation forKey:nil];
}
This is what it looks like:
The red area should be the video playing.
You can check out the sample project I created here:
https://www.dropbox.com/s/jxuh69wiqdwnuc6/PlayerLayer%20size%20Animation.zip?dl=1
The frame property of a CALayer is a derived property, dependent on the position, anchorPoint, bounds and transform of the layer. Instead of animating the frame, you should instead animate the position or bounds, depending on what effect you are trying to accomplish.
You may first take a look at this:
Q: When I try to animate the frame of a CALayer nothing happens. Why?
This can also help:
How to animate the frame of an layer with CABasicAnimation?
And those code may be helpul to you:
NSURL *fileUrl = [NSURL fileURLWithPath:#"yourFilePath"];
AVPlayer *player = [[AVPlayer alloc] initWithURL:fileUrl];
CALayer *superlayer = self.view.layer;
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
[superlayer addSublayer:playerLayer];
[player play];
playerLayer.videoGravity = AVLayerVideoGravityResize;
playerLayer.frame = CGRectMake(10, 200, 320, 200);
playerLayer.position = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2);
CGRect startBounds = CGRectMake(10, 200, 200, 150);
CGRect endBounds = CGRectMake(10, 200, 320, 200);
CABasicAnimation * sizeAnimation = [CABasicAnimation animationWithKeyPath:#"bounds"];
sizeAnimation.fromValue = [NSValue valueWithRect:startBounds] ;
sizeAnimation.toValue = [NSValue valueWithRect:endBounds] ;
sizeAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
sizeAnimation.duration = 4;
[playerLayer addAnimation:sizeAnimation forKey:#"bounds"];

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.

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];

how to set the frame rate for CABasicAnimation when animation begins in iPhone sdk

I am having multiple Imageview's where I am dividing the 360 degrees/no.of.Imageviews to get an angle to rotate each Imageview.
#define angle ((2*M_PI)/13.0)
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
mainView.backgroundColor = [UIColor blackColor];
for(currentIndex = 0; currentIndex < 13; currentIndex++)
{
UIImageView *imageView = [self getCardsWithAngle:(currentIndex*angle)];
[mainView addSubview:imageView];
}
}
-(UIImageView *)getCardsWithAngle:(float)aAngle
{
CGRect frame = CGRectMake(mainView.center.x+50, mainView.center.y-100, 250,200);
CGPoint anchorPoint = CGPointMake(0.5,1.5);
imgView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"card_1.png"]];
imgView.frame = frame;
imgView.backgroundColor = [UIColor clearColor];
if(aAngle == 0.0)
{
imgView.layer.anchorPoint = anchorPoint;
return imgView;
}
imgView.layer.anchorPoint = anchorPoint;
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat:aAngle];
rotationAnimation.duration = 3.0;
rotationAnimation.removedOnCompletion = NO;
rotationAnimation.fillMode = kCAFillModeForwards;
rotationAnimation.delegate = self;
[rotationAnimation setValue:[NSNumber numberWithFloat:aAngle] forKey:#"finalAngle"];
[imgView.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
return imgView;
}
Here My problem is all the imageviews start animating at a time. I want to rotate the image views to an angle one after another.
Any ideas will be appreciated,
Thanks all

Using a CALayer as a mask to a backing layer of a GLKView blocks touch events?

I'm trying to use a CAShapeLayer as a mask to the backing layer of a GLKView like so:
CGRect circleFrame = CGRectMake(0, 0, 800, 800);
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:circleFrame];
CGPoint circleAnchor = CGPointMake(CGRectGetMidX(circleFrame) / CGRectGetMaxX(circleFrame),CGRectGetMidY(circleFrame) / CGRectGetMaxY(circleFrame));
shapeLayerMask = [[CAShapeLayer alloc] init];
shapeLayerMask.path = path.CGPath;
shapeLayerMask.anchorPoint = circleAnchor;
shapeLayerMask.frame = CGRectMake(0, 0,800, 800);
shapeLayerMask.fillColor = [UIColor blackColor].CGColor;
shapeLayerMask.backgroundColor = [UIColor clearColor].CGColor;
shapeLayerMask.opacity = 1.0f;
shapeLayerMask.hidden = NO;
self.view.layer.mask = shapeLayerMask;
But as soon as i do this, all my touch events stop firing. No more touchesBegan, touchesEnded, etc. Anyone knows why? Thanks in advance!