I'm getting some weird bugs. I thought I had a handle on this. I copied code from one of my other projects. I'm wondering if it could be something to do with my custom class.
I have a view controller initialize my custom class called "TitleCardView"
Inside there I have a bunch of animations like this one:
CGPoint startPointb = borderMaskLayer.position;
CGPoint endPointb = CGPointMake(borderMaskLayer.position.x, borderMaskLayer.position.y-1000);
CABasicAnimation* bmoveAnim = [CABasicAnimation animationWithKeyPath:#"position"];
bmoveAnim.delegate=self;
[bmoveAnim setValue:#"borderMaskAnim1" forKey:#"id"];
bmoveAnim.fromValue = [NSValue valueWithCGPoint:startPointb];
bmoveAnim.toValue = [NSValue valueWithCGPoint:endPointb];
bmoveAnim.duration = 1;
[bmoveAnim setBeginTime:CACurrentMediaTime()+.4];
bmoveAnim.fillMode = kCAFillModeForwards;
bmoveAnim.removedOnCompletion = NO;
bmoveAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[self.borderLayer.mask addAnimation:bmoveAnim forKey:#"position"];
[self.borderWhiteLayer.mask addAnimation:bmoveAnim forKey:#"position"];
The animation works fine but when I try to implement AnimationDidStop{, as soon as the view loads, all the animations get logged by the delegate method with the FALSE (did not finish) flag.
I added a button and tried to use:
[UIView animateWithDuration:6 animations:^{
continueButton.alpha = 1.0f;
}];
and this code with the delay parameter....
Same problem. As soon as the view loads, its like the animation gets run immediately with a duration of 0.
Are you not supposed to add animations in your init method? I feel like there must be a rule I'm breaking that I don't know about.
This code does work:
this button is the last thing in the init method
continueButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[continueButton addTarget:self
action:#selector(titleNext)
forControlEvents:UIControlEventTouchUpInside];
[continueButton setTitle:#"Click to Continue" forState:UIControlStateNormal];
continueButton.frame = CGRectMake(0.0, 390.0, 320.0, 75.0);
[self addSubview:continueButton];
continueButton.alpha = 1.0;
then this is the method it calls
-(void)titleNext{
// proceeds to the motto page from the title page
[UIView animateWithDuration:.6 animations:^{
continueButton.alpha = 0.0f;
}];
}
So can anyone tell me why my animations are acting weird???
I recreated the project and found the animationDidStop delegate worked when the animations were created in -(void)viewDidAppear{ instead of -(void)viewDidLoad{
For swift it works if the animations are in
override func viewDidAppear(_ animated: Bool)
Related
I programmed my own view containing an imageview which should be rotating. Here is my rotation animation:
- (void)startPropeller
{
//_movablePropeller = [[UIImageView alloc] initWithFrame:self.frame];
//_movablePropeller.image = [UIImage imageNamed:#"MovablePropeller"];
//[self addSubview:self.movablePropeller];
self.hidden = NO;
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.
[self.movablePropeller.layer removeAllAnimations];
[self.movablePropeller.layer addAnimation:rotation forKey:#"Spin"];
}
And here is how I start it:
self.loadingPropeller = [[FMLoadingPropeller alloc] initWithFrame:self.view.frame andStyle:LoadingPropellerStyleNoBackground];
self.loadingPropeller.center=self.view.center;
[self.view addSubview:self.loadingPropeller];
[self.loadingPropeller startPropeller];
Problem is: Without any further code. The propeller is not rotating. So I was able to solve it by adding this code into my class implementing to rotating propeller spinner:
-(void)viewDidAppear:(BOOL)animated
{
if(!self.loadingPropeller.hidden){
[self.loadingPropeller startPropeller];
}
}
But I don't like that too much. Isn't it possible to add some code within the Propeller class to solve this issue automatically, without having to add also code in every class in the viewDidAppear method?
The code that doesn't work does two essential things: adding the spinner to the view hierarchy and positioning it. My guess is that the failure is due to positioning it before layout has happened. Try this:
// in viewDidLoad of the containing vc...
self.loadingPropeller = [[FMLoadingPropeller alloc] initWithFrame:CGRectZero andStyle:LoadingPropellerStyleNoBackground];
[self.view addSubview:self.loadingPropeller];
// within or after viewDidLayoutSubviews...
// (make sure to call super for any of these hooks)
self.loadingPropeller.frame = self.view.bounds;
self.loadingPropeller.center = self.view.center;
// within or after viewDidAppear (as you have it)...
[self.loadingPropeller startPropeller];
This may sound really noob, but i've spent an entire day wrestling with this problem and would appreciate some help.
You see i have a method which I call more than once inside gameplay. If I use [self myMethod]; then it works for ONE time. And then when I call it again, the animation in the method doesn't commence anymore.
What I need is to replace "self" with an alternative that can be "alloc'ed" and "released" to make my animations work.
I've tried;
#implementation gameViewController
gameViewController *object = [[gameViewController alloc] init];
[object myMethod];
However the above substitute for self doesn't even call on the method. I don't know what I did wrong, it's suppose to work just like "self".
Is there something i missed? How do you make an object of the class to work just like "self" does?
Thanks so much.
Here is a more detailed look of my code;
[self explosionAnimations];
- (void) explosionAnimations
{
UIImage *image = [UIImage imageNamed: #"Yellow Explosion.png"];
[bomb setImage:image];
[UIView beginAnimations:#"bomb1ExplosionIncrease" context:NULL];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelegate:self];
bomb.transform = CGAffineTransformMakeScale(3.4, 3.4);
[UIView commitAnimations];
}
The setImage works fine every time. But the animations stop working on the second call of the method. While in the console it logs "animation completed" with nothing happening to the image.
This leads me to believe that somehow "self" believes that the animation was already done and will not bother to do it again. So I thought a new "alloc" might give it a kick awake.
The problem doesn't have anything to do with "self". The problem is that you set the transform in your animation, and then when you run it again, you're setting the same transform, so it does nothing. You need to reset the frame to the new frame and then set the transform back to the identity transform before you do the animation again. Also, you should be using block based animations. I'm not sure this is the best way to do it, but this worked for me (if you have auto layout turned off).
- (void)explosionAnimations {
UIImage *image = [UIImage imageNamed: #"Yellow Explosion.png"];
[self.bomb setImage:image];
[UIView animateWithDuration:.5 animations:^{
self.bomb.transform = CGAffineTransformMakeScale(3.4, 3.4);
} completion:^(BOOL finished) {
CGRect newFrame = self.bomb.frame;
self.bomb.transform = CGAffineTransformIdentity;
self.bomb.frame = newFrame;
}];
}
If you're doing this in an app with auto layout turned on (which it is by default), then I would not use a transform, but just resize the width and height of the image view by adjusting its height and width constraints. So, in this method, you should make IBOutlets to height and width constraints you make in IB, then change their constant values in an animation block:
[UIView animateWithDuration:.5 animations:^{
self.heightCon.constant = self.heightCon.constant * 3.4;
self.widthCon.constant = self.widthCon.constant * 3.4;
[self.view layoutIfNeeded];
}];
I have 1 year works with Objective-C and is my first question that I make because I always found the answer in this site, but this situation is completely insane and did not found anything similar.
I have a custom class (called BallClass from UIView). The UIView have an UIImageView inside on it and a UITapGesture to make an action.
So when I create the object in a UIViewController, does not works the tapAction when press in the object, but if I change to other screen and return it, the tapGesture works perfect.
I tried many options, but I cant makes work in the first load.
I work with ARC, Xcode (4.6.3) and OSX (10.8.4) and IB but this objects was created with no IB .
Thanks in advance.
Here is the class BallClass.m
- (void)tapGesture:(UIGestureRecognizer *)tapGesture {
NSLog(#"hola");
}
- (id)initBall:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
// Recibir las variables iniciales para el control del timer
ball = [[Ball alloc] init];
// Agregar la imagen de la pelota en el view
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height)];
[imageView setCenter:CGPointMake(CGRectGetMidX([self bounds]), CGRectGetMidY([self bounds]))];
[imageView setContentMode:UIViewContentModeScaleAspectFit];
[imageView setImage:[UIImage imageNamed:#"pelota.png"]];
[imageView setTag:100];
[imageView setNeedsDisplay];
// Add TAP
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapGesture:)];
// Tap Properties
[tapGesture setDelaysTouchesBegan:YES];
[tapGesture setNumberOfTapsRequired:1];
[tapGesture setDelegate:(id)self];
[imageView addGestureRecognizer:tapGesture];
[self addSubview:imageView];
[self setNeedsDisplay];
// Propiedades del View
[self setUserInteractionEnabled:YES];
self.backgroundColor = [UIColor clearColor];
[self setTag:100];
// BALL BOUNCE
[NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0 target:self selector:#selector(onTimer:) userInfo:nil repeats:YES];
}
return self;
}
- (void)onTimer:(NSTimer *)timerLocal {
// Do something
}
... // more methods
Here is the instantiate:
ballView00 = [[BallView alloc] initBall:CGRectMake(10, 10, 40, 40)];
The UiView show perfect, and the animation works fine, but the UITapGesture just works, as mentioned early, after reLoad the ViewController.
Here is the link to image the Ball and UIView (#kBall). Sorry, about image, but I cant load until I have 10 points :(
This might be an issue with the superview's frame. What view is holding ballView00? Does ballView lie entirely within it's superview's bounds?
Usually when you encounter problem where you're expecting a touch but don't get it it's because the view set to receive the touch is outside one of its parents' bounds.
So if the superview bounds are 100x100 and the ball is at x=125 y=125 it will not receive a touch.
A lot of magic happens when your views get laid out, it's not surprising to sometimes find one with 0x0 dimensions because of some incorrect constraints.
You can easily check the bounds of all your views by giving them a background color.
I found the solution.
The ball make a spin with this:
[UIView animateWithDuration: 0.5f
delay: 0.0f
options: UIViewAnimationOptionCurveEaseIn
animations: ^{
self.transform = CGAffineTransformRotate(self.transform, M_PI / 2);
}
completion: ^(BOOL finished) {
if (finished) {
if (animating) {
// if flag still set, keep spinning with constant speed
[self spinWithOptions: UIViewAnimationOptionCurveLinear];
}
}
}];
And generate conflict with the main timer. I change the spin action with simple add self.transform = CGAffineTransformRotate(self.transform, M_PI / 2); inside drawRect method in the class.
Apparently, I can't nested two timers, because "disable" the tapGesture, so when I change of the screen and return, the spin was stopped and this help to found this situation.
Thanks for your help.
I am creating buttons programatically, and they are animated (fade & movement), I need the buttons to still have user interaction though. I have tried things like
options:(UIViewAnimationOptionAllowUserInteraction
which seems to do nothing :/ I have read several posts on here with different advice, including someone saying it isn't actually possible. Does anyone know if I can enable user interaction while a button is animating, and if it's not possible how do people get around this issue?
Thank you.
This is the current animation code that I was using:
[UIView animateWithDuration:106
delay:0
options:(UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionAllowAnimatedContent | UIViewAnimationOptionCurveEaseInOut)
animations:^{
[_planetButton setCenter:point];
_planetButton.alpha = 0.2;
_planetButton.userInteractionEnabled = YES;
}
completion:^(BOOL finished){
NSLog(#"animation completed");
//[_planetButton removeFromSuperview];
}
];
I also tried animation such as:
CABasicAnimation *move = [CABasicAnimation animationWithKeyPath:#"transform2"];
move.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
move.duration = 5.105;
move.repeatCount = 1;
move.autoreverses = YES;
move.removedOnCompletion = YES;
move.toValue = [NSValue valueWithCGPoint:point];
[_planetButton.layer addAnimation:move forKey:nil];
I have a button on my UI that I would like to flash (turn on and then off again) every 800ms, once the button has been pressed. I do that with the following code:
- (void)flickEmergencyButton {
// Check whether an emergency is in progress...
if (model.emergencyInProgress) {
// ...and if so, flick the state
self.emergencyButton.selected = !self.emergencyButton.selected;
// Make this method be called again in 800ms
[self performSelector:#selector(flickEmergencyButton) withObject:nil afterDelay:0.8];
} else {
// ...otherwise, turn the button off
self.emergencyButton.selected = NO;
}
}
...which works really well, except: There is a UIScrollView on the UI as well and while the user has his finger down on it and is scrolling around, the button freezes. While I completely understand why that is, I am not sure what to do about it.
The performSelector:withObject:afterDelay message schedules the message to be send on the current thread, which is the main thread, ie. the UI tread and hence does not get to process the message until all other UI activity has come to an end. Correct? But I need to do this on the UI thread as I cannot select/un-select the button on any other thread, right? So what is the solution here?
I would recommend using Core Animation. Try something like this:
-(void) flash{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3f];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
if( emergency ){
// Start flashing
[UIView setAnimationRepeatCount:1000];
[UIView setAnimationRepeatAutoreverses:YES];
[btn setAlpha:0.0f];
}else{
// Stop flashing
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationRepeatCount:1];
[btn setAlpha:1.0f];
}
emergency = !emergency;
[UIView commitAnimations];
}
Where btn is declared as
#property(nonatomic, retain) IBOutlet UIButton *btn;
and emergency is a simple BOOL variable.
Call flash to start and to stop the animation.
In this example we animate the alpha attribute for simplicity, but you can do the same with the button backcolor, as Sam said in his answer, or whatever attribute you like.
Hope it helps.
UPDATE:
Regarding making the transition between two images, try calling imageFlash instead of flash:
-(void) imageFlash{
CABasicAnimation *imageAnimation = [CABasicAnimation animationWithKeyPath:#"contents"];
[btn setImage:normalState forState:UIControlStateNormal];
if( emergency ){
imageAnimation.duration = 0.5f;
imageAnimation.repeatCount = 1000;
}else{
imageAnimation.repeatCount = 1;
}
imageAnimation.fromValue = (id)normalState.CGImage;
imageAnimation.toValue = (id)emergencyState.CGImage;
[btn.imageView.layer addAnimation:imageAnimation forKey:#"animateContents"];
[btn setImage:normalState forState:UIControlStateNormal]; // Depending on what image you want after the animation.
emergency = !emergency;
}
Where normalState and emergencyState are the images you want to use:
Declared as:
UIImage *normalState;
UIImage *emergencyState;
Assigning the images:
normalState = [UIImage imageNamed:#"normal.png"];
emergencyState = [UIImage imageNamed:#"alert.png"];
Good luck!
While this feels like a job for CoreAnimation (perhaps animating the backgroundColor of a custom UIControl) you could accomplish this with an NSTimer running in the appropriate run loop mode.
e.x.
NSTimer *timer = [NSTimer timerWithTimeInterval:0.8f target:self selector:#selector(flicker:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
and when you want to stop animation:
[timer invalidate], timer = nil;
button.selected = NO;
By adding the timer to all NSRunLoopCommonModes, you not only add it to the default run loop mode but the mode it is in while user interaction is being continuously processed (UITrackingRunLoopMode.)
Apple's docs give a more thorough explanation of run loop modes and run loops in general.