How to flash a button on UI thread? - objective-c

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.

Related

CGRectMake Does not move image (Sometimes)

When click on the image in my app and the image is at the top, the image will always move down perfectly. But when the image is at the bottom sometimes the image does not move up. Even though it does not move the process is still run and it logs as if it was at the top. I tried everything I know how to do but do you see anything wrong? Thanks!!
- (void )imageTapped:(UITapGestureRecognizer *) gestureRecognizer
{
NSLog(#"imageTapped");
[UIView beginAnimations:#"move" context:nil];
[UIView setAnimationDuration:0.3];
if (self.imgV.frame.origin.y == 20) {
NSLog(#"Top");
upOrDown = #"1";
self.invalidateTheTimer = #"";
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateLabel) userInfo:nil repeats:YES];
self.imgV.frame=CGRectMake(0, self.view.frame.size.height-self.imgV.frame.size.height, self.imgV.frame.size.width, self.imgV.frame.size.height);
self.vBottom.frame=CGRectMake(0, self.imgV.frame.origin.y+self.imgV.frame.size.height, self.vBottom.frame.size.width, self.vBottom.frame.size.height);
} else if (self.imgV.frame.origin.y >= 507) {
NSLog(#"Bottom");
self.imgV.frame=CGRectMake(0, 20, self.imgV.frame.size.width, self.imgV.frame.size.height);
self.vBottom.frame=CGRectMake(0, self.imgV.frame.origin.y+self.imgV.frame.size.height, self.vBottom.frame.size.width, self.vBottom.frame.size.height);
[hour setText:#""];
[minute setText:#""];
[second setText:#""];
[timer invalidate];
self.invalidateTheTimer = #"INVALIDATE";
}
NSLog(#"%f", self.imgV.frame.origin.y);
[UIView commitAnimations];
}
Considering you haven't posted your entire code, it's hard to figure out what's wrong exactly, but looking at the mess you've made, I suggest you take a look into block based animations which should simplify what you're trying to accomplish.
Additionally, if I had to guess, I'd say the issue is in your timer and whatever method it's invoking is probably messing with your animations.

Xcode Animations complete instantly - CAAnimations call didStop - flag = FALSE

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)

Alternative to "self" in calling methods in Objective-C

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

Objective c - adding threading to UIView animation method results in only one animation running

I've written a method, sortAndSlideHandCards(), that moves 6 UIButtons. Each UIButton is moved to the same position. This is done via a for-each loop and the animateWithDuration method being called on each UIButton.
This method is called for a number of players at the same time. Currently the behaviour results in UIButtons from each player moving but only one at a time. No more than one UIButton can move at any time, as if each animation is waiting for whatever animation that is currently running to stop before attempting it's own animation, essentially the code is executed sequentially for each player/UIButton. I hoped threading would help me fix this.
when I added the threading code:
backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
sortAndSlideHandCardsGroup = dispatch_group_create();
for(Player* player in _playersArray) {
dispatch_group_async(sortAndSlideHandCardsGroup, backgroundQueue, ^(void) {
[player sortAndSlideHandCards];
});
dispatch_group_wait(sortAndSlideHandCardsGroup,DISPATCH_TIME_FOREVER);
I found that only the first UIButton animation is triggered for each player and that the code gets held up in the runloop "while" because "_animationEnd" never gets set as it would appear the second animation never gets going.
I can see the method launching in its own thread
- (void) sortAndSlideHandCards {
NSLog(#"PLAYER:sortAndSlideHandCards");
CGPoint newCenter;
Card* tempCard = nil;
int count = 1;
float duration = 0.2 / _speedMultiplyer;
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
for(Card *card in _handCards) { //move cards in hand to one postion in hand
if(count == 1) {
tempCard = [[Card alloc] init:_screenWidth:_screenHeight :[card getNumber] :[card getCardWeight] :[card getSuit] :[card getIsSpecial]];
[tempCard setImageSrc: _playerNumber :!_isPlayerOnPhone :count : true :_view: _isAI: [_handCards count]];
newCenter = [tempCard getButton].center;
}
_animationStillRunning = true;
if(![[DealViewController getCardsInPlayArray] containsObject:card] ) {
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionLayoutSubviews animations:^{[card getButton].center = newCenter;} completion:^(BOOL finished){[self animationEnd];}];
while (_animationStillRunning){ //endAnimation will set _animationStillRunning to false when called
//stuck in here after first UIButton when threading code is in play
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
} //endAnimation will set _animationStillRunning to false when called
}
count++;
}
}
When i comment out the threading code each UIButton (Card) will animate one after another.
With the threading code is in play the first UIButton will animate but during the second run through the for-loop, the code will be stuck in the while-loop, waiting for the animation to end. I'm guessing the second animation doesn't even start.
I also tried this for the threading code:
[player performSelectorInBackground:#selector(sortAndSlideHandCards) withObject:nil];
Same outcome
Anyone have any ideas why animateWithDuration doesn't like getting called in a loop when in a thread other than the main one?
You should just be able to kick off the animations you want from whatever UI action is performed. The UIView animateWith... methods return immediately, so you don't need to worry about waiting for them to complete.
If you have an unknown number of animations to kick off sequentially, use the delay parameter. Pseudocode:
NSTimeInterval delay = 0;
NSTimeInterval duration = 0.25;
for (UIView *view in viewsToAnimate)
{
[UIView animateWithDuration:duration delay:delay animations:^{ ... animations ...}];
delay += duration;
}
This will increase the delay for each successive animation, so it starts at the end of the previous one.

Press and hold to make Image move

I have a button and an image. When the button is pressed and held I want the image to movie across the screen quickly. Right now after I press and hold it just moves after I let go. I need it to start moving after a certain amount of time and stop when I let go.
here is what I have:
- (IBAction)buttonDown:(id)sender
{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
x = x + 100;
}
With below code, you have to push 3 seconds to move _imageView, and then the image begins moving, and when you release the button, the image stops.
- (IBAction)pushToMove:(id)sender {
[self performSelector:#selector(move) withObject:nil afterDelay:3.0];
}
- (void)move
{
_nt = [NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:#selector(goRight)
userInfo:nil
repeats:YES];
}
- (void)goRight
{
_imageView.frame = CGRectMake(_imageView.frame.origin.x + 10, _imageView.frame.origin.y,
_imageView.frame.size.width, _imageView.frame.size.height);
}
- (IBAction)stop
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(move) object:nil];
[_nt invalidate];
}
I put this sample project to GitHub. You can download and just run it.
https://github.com/weed/p120804_ImageMoveDuringPushButton
This can be done in interface builder by simply liking to the buttons touch down property instead of touch up inside. In code it looks like this:
[myButton addTarget:self action:#selector(myMethod) forControlEvents:UIControlEventTouchDown];
In addition to this, you'll want to do some state checking on your animation, and use UIViewAnimationOptionBeginFromCurrentState as an option to help it pick up where it left off when the user presses the button again.