Changing view background image in loop - ios

I have 3 images for setting self.view.background . What i' am trying , setting background like Twitter iOS APP Login UI ( Changing background image randomly in every 5 second. )
All random process has done , what i really in trouble is , when i am using UIAnimation it randomise it sync mode. When it works, whole UI waits UIAnimation process as expected .
here is my code : ( I am calling this method at the end of the viewDidLoad method :
Note: also , setting alpha is not working ..
-(void)startBackgroundImageChange
{
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:5.0
delay:0.2
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
self.view.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:[self setBackgroundImage]]];
self.view.alpha = 1.0;
//here you may add any othe actions, but notice, that ALL of them will do in SINGLE step. so, we setting ONLY xx coordinate to move it horizantly first.
}
completion:^(BOOL finished){
//here any actions, thet must be done AFTER 1st animation is finished. If you whant to loop animations, call your function here.
self.view.alpha = 0.3;
[self startBackgroundImageChange];
}];
});
}
Any help will be appreciated ..

Related

How to force a view animation to finish before executing sendSubviewToBack?

I am building a page browser that animates pages as 'sheets of paper' being pulled on top of off a stack of papers. In order to keep smooth animations I use 3 UIViews which are stacked on top of each other. These three views hold the current page (on top), the previous page (in the middle) and the next page (at the bottom).
In the code below, I want to drag the top view off to the right, revealing the previous page underneath. This works fine. However, after that I need to move the top page to the bottom of the stack, in order to prepare the stack of three views for the next time the user does a page flip. I use the sendSubviewToBack method for this.
My problem is that ViewSample[Top] is sent to the bottom of the stack as soon as the animation starts. How can I enforce the animation to finish (so that ViewSample[Top] has moved out of the screen completely) before it is sent to the bottom of the stack?
ViewSample[Top].center = CGPointMake(x,y);
[UIView animateWithDuration:0.5
animations:^
{
ViewSample[Top].center = CGPointMake(x+w,y); //slide away to the right
}
completion:^(BOOL finished)
{
}
];
[self.MainView sendSubviewToBack:ViewSample[Top]];
EDIT
i just ran into a very peculiar behaviour which has to do with my problem.
I followed your advice, and found that the behaviour in the 'finished' section of the animation depends on the value of the variable 'top' when it is set AFTERWARDS:
ViewSample[Top].center = CGPointMake(x,y);
[UIView animateWithDuration:0.5
animations:^
{
ViewSample[Top].center = CGPointMake(x+w,y); //slide away to the right
}
completion:^(BOOL finished)
{ [self.MainView sendSubviewToBack:ViewSample[Top]];
}
];
Top++; // THIS COMMAND AFFECTS THE LINE ABOVE!!!
In other words, when I add the line 'Top++;' another View is moved back on the stack, even though the statement sendSubviewToBack came first. This is very confusing to me. Does this make sense? Is it a bug?
The other answers correctly identified the issue. What you're running into with your updated code is a problem of execution order:
Because completion is executed only after the animation completes, your code actually executes in this order:
ViewSample[Top].center = CGPointMake(x,y);
ViewSample[Top].center = CGPointMake(x+w,y);
Top++;
[self.MainView sendSubviewToBack:ViewSample[Top]];
There are two possible solutions. You can either store the view in a variable so you have the same view in all your calls, or you can delay setting the value of Top until completion.
Option 1
UIView *viewMovingFromTopToBottom = ViewSample[Top];
viewMovingFromTopToBottom.center = CGPointMake(x,y);
[UIView animateWithDuration:0.5 animations:^{
viewMovingFromTopToBottom.center = CGPointMake(x+w,y); //slide away to the right
} completion:^(BOOL finished) {
[self.MainView sendSubviewToBack:viewMovingFromTopToBottom];
}];
Top++;
// Other code that depends on the new value of Top...
Option 2
ViewSample[Top].center = CGPointMake(x,y);
[UIView animateWithDuration:0.5 animations:^{
ViewSample[Top].center = CGPointMake(x+w,y); //slide away to the right
} completion:^(BOOL finished) {
[self.MainView sendSubviewToBack:ViewSample[Top]];
Top++;
// Other code that depends on the new value of Top...
}];
Which option makes the most sense to you depends on what you're doing. If you're chaining animations together, you may want to move most of your code into the completion block to delay it until the slide animation completes. If you have a lot of work that needs to be done right away without dependencies on animation, you may want to use option 1 to configure you animations and move on. Or you may want a mix.
Use the completion block:
ViewSample[Top].center = CGPointMake(x,y);
[UIView animateWithDuration:0.5
animations:^
{
ViewSample[Top].center = CGPointMake(x+w,y); //slide away to the right
}
completion:^(BOOL finished)
{
[self.MainView sendSubviewToBack:ViewSample[Top]];
}
];
Brian Nickel's first suggestion (local variable) did the trick. However, there was a caveat: you have to be careful in the order of statements. This does not work:
[self.MainView addSubview:LocalView];
LocalView = View[1];
...whereas this does:
LocalView = View[1];
[self.MainView addSubview:LocalView];
I first had the top version, which just makes the blank view appear.
So a working approach is to use three global views to do the page caching, and use two local views for the animation. The local views are stacked in the appropriate order, copy the data from the global views and then perform the animation.

Would like to show a new view after a view animation is finished

I have a animation view. When the animation is finished I would like to show a new view by setting its hidden to false.
I set the hidden to false after the animation block code, but it seems like the animation is being done on a separate thread. The view gets unhidden while the animation block is still playing
// start animation block
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:2];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut ];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:mCards[r] cache:YES];
// mCards[0].image = [UIImage imageNamed:#"card.png"]; //begin
int id=TarretWheel[r];
mCards[r].image = gCardImages[id]; //end
// Start animtion
[UIView commitAnimations];
// show view
mBackground.hidden=false;
You can (and preferably should, actually), be using the newer block-based animation methods on UIView. Check out the following (it has a completion block which is just what you need):
[UIView animateWithDuration:0.5 animations:^{
} completion:^(BOOL finished) {
}];
Edit:
There are other variants with options that you may want in your case.
Also, clarification on why you 'should' be using the block-based methods; from the docs (for beginAnimations:context:):
Use of this method is discouraged in iOS 4.0 and later. You should use
the block-based animation methods to specify your animations instead.
My answer is same as Joe but I would recommend using a different API which allows you with configure block.
[UIView animateWithDuration:2
delay:0.0
options:UIViewAnimationOptionTransitionFlipFromLeft|UIViewAnimationOptionCurveEaseOut |
animations:^{
// mCards[0].image = [UIImage imageNamed:#"card.png"]; //begin
int id=TarretWheel[r];
mCards[r].image = gCardImages[id]; //end
}
completion:^(BOOL finished){
mBackground.hidden=false;
}
];

iOS: UIView Animation Refactoring

Whilst playing around with UIView animation, I came across a situation where I think some refactoring is needed:
The following views whose opacity are initially set to 0.0f.
Ex:
[UIView animateWithDuration:1.0f
animations:^
{
firstView.layer.opacity = 1.0f;
}
completion:^(BOOL finished)
{
[UIView animateWithDuration:1.0f
animations:^
{
secondView.layer.opacity = 1.0f;
firstView.layer.opacity = 0.0f;
}
completion:^(BOOL finished)
{
[UIView animateWithDuration:1.0f
animations:^
{
thirdView.layer.opacity = 1.0f;
secondView.layer.opacity = 0.0f;
}
completion:^(BOOL finished)
{
thirdView.layer.opacity = 0.0f;
}];
}];
}];
All 3 views are just subclass of UIView's, which are added as subviews of the main view.
This simply animates the opacity of the first view to 1.0f and then that of the second view, and then that of the third view.
Simple. Nothing special here.
My Question is:
What if I had more views, let say 100, that I wanted to perform the same action (same sequence of animation), this block of code would expand and expand.
So for the sake of refactoring and being adhered to good practice of writing code, I thought may be this could be done with less code via the use of a method and perhaps a loop.
Could you enlighten me on this in regards to refactoring; in addition, would dispatch_apply be useful here along with the refactoring process if a loop is needed?
If you wanted to animate 100 images, you would probably want to use 2 views and load alternating images into each one. I recently created a sample app on github that does exactly that:
Animating UIImages with cross-fade opacity changing

iOS Graphic Appear and Fade Out with Alpha

I've been reading about UIView animateWithDuration which I'm trying to use so when a button is pressed a graphic appears then slowly fades out (i.e. alpha is set to 0).
I'm using the code below in my viewdidload just for test purposes however its not working:
[UIView animateWithDuration:10 animations:^{
self.completeImage.alpha = 1.0;
self.completeImage.alpha = 0.5;
self.completeImage.alpha = 0.0;
}];
Any ideas?
Thanks.
That is not working because automatically it sets the alpha to 0.0;
The 3 lines of code are executed at the same time (one after the other).
The proper way to use the UView animation block it is like this:
self.completeImage.alpha = 0.0;
[UIView animateWithDuration:2.0
animations:^{
// do first animation
self.completeImage.alpha = 1.0;
}
completion:^(BOOL finished){
[UIView animateWithDuration:2.0
animations:^{
// do second animation
self.completeImage.alpha = 0.0;
}
completion:^(BOOL finished){
;
}];
}];
Hope this achieve what you are looking for.
In addition:
" I'm trying to use so when a button is pressed a graphic appears
then slowly fades out (i.e. alpha is set to 0)."
As per your above information in the question, addition of the code in viewDidLoad will not prove fruitful. You need to add this code in the action target method of your button in order to play the animation on click of a button. Generally if you're using the nib, then the action method will be like below:
-(IBAction)on_pressing_my_button:(id)sender
{
///your animation code goes here..
}

2 animations on same object, only last shows. How to show first one w/o nesting completion block?

I'm doing two animations on the same UIImageView, using blocks. Animations are not quite back to back, but almost; there is some logic inbetween.
animate UIImageView from one location on the view to another.
execute logic that determines whether image is allowed to stay there
if not allowed, undo the animation (UIImageView springs back to original location)
If I implement this as above, only the second animation shows (this is normal behavior from what I understand). If I nest the logic and the second animation block inside the completion block of the first, I see both animations, but there's a fair amount of code to jam into that completion block and it just seems ugly and out of place.
In the non-nested configuration, why does iOS want to cut short the previous animations and execute only the final one, and how can I force it to wait on the first one before going to the next? I don't think it needs to block the main thread or "sit and spin" in a completion block; I just want all animations to be shown. Tried adding delay to second animation to no avail.
Is this a job for CAKeyframeAnimation?
// first animation
[UIView animateWithDuration:0.5
delay:0.0
options: UIViewAnimationCurveLinear
animations:^{movingColor.center = newCenter;
movingColor.transform = scaleTransform;}
completion:^(BOOL finished){ NSLog(#"forward animation done");}];
if (/* ...color is allowed at that location */) {
// do some stuff
} else {
// undo the animation
[UIView animateWithDuration:0.5
delay:0.0
options: UIViewAnimationCurveLinear
animations:^{movingColor.center = origCenter;
movingColor.transform = scaleTransform;}
completion:^(BOOL finished){ NSLog(#"back animation done");}];
}
The second animation should be done conditionally inside the first's completion block. Putting it into a function will make it more readable.
- (void)moveColor:(UIView *)view to:(CGPoint)center delay:(NSTimeInterval)delay completion:(void (^)(BOOL finished))complete {
[UIView animateWithDuration:0.5 delay:delay options:UIViewAnimationCurveLinear animations:^{
view.center = center;
view.transform = scaleTransform; // probably don't need this
} completion:completion];
}
This way your logic can be separate from the animation code
- (void)someMethod {
[self moveColor:movingColor to:newCenter delay:0.0 completion:^(BOOL finished) {
if (/*it can stay*/) {
// stuff
} else {
[self moveColor:movingColor to:origCenter delay:2.0 completion:^(BOOL finished) {}];
}
}];
}
The correct way to do it is as you said to set the second one in the completition of the first one that is the correct way,
You can also adda delay on the start of the other, this may or may not work it depends on alot of variables
Your second animation will have a delay of .5 (time for first animation to complete )
[UIView animateWithDuration:0.5
delay:0.5
options: UIViewAnimationCurveLinear
animations:^{movingColor.center = origCenter;
movingColor.transform = scaleTransform;}
completion:^(BOOL finished){ NSLog(#"back animation done");}];
The animateWithDuration is executed asynchronously, and returns right away. That is, the next line of code is executed without waiting for the animation to finish. You either put your code in the completion block if you want it to be executed after the animation is finished, or you accept that the second animation is started (thus canceling the first) immediately.
If you want to indicate to the user that something was wrong by starting the animation, but not completing it, you could execute the second animation with a delay. Remember to also set the the UIViewAnimationOptionBeginFromCurrentState option if you delay the second animation with less than the duration of the first:
[UIView animateWithDuration:0.5
delay:0.25 //or some number
options: (UIViewAnimationCurveLinear | UIViewAnimationOptionBeginFromCurrentState)
animations:^{movingColor.center = origCenter;
movingColor.transform = scaleTransform;}
completion:^(BOOL finished){ NSLog(#"back animation done");}];

Resources