Setting UIView's frame property in overlapping animation blocks causes jump - objective-c

(iOS 5.1, XCode 4.4)
In a project I am working on, I have two controllers that both set the frame property of the same UIView (to set size and position, respectively), in separate animation blocks that temporally overlap (the second animation block is started before the first one finishes). This seems to cause an odd jump (unanimated change) in the position of the UIView.
Minimal example to reproduce (place in viewcontroller of a new single view project (iphone), and hook up some button to the action method).
#interface ViewController ()
#property (nonatomic, strong) UIView *viewA;
#end
#implementation ViewController
- (IBAction)buttonPressed
{
// Animate
[UIView animateWithDuration:5 animations:^{
self.viewA.frame = CGRectMake(0, self.viewA.frame.origin.y, 320, 200);
}];
[UIView animateWithDuration:5 animations:^{
self.viewA.frame = CGRectMake(0, 200, 320, self.viewA.frame.size.height);
}];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.viewA = [[UIView alloc] init];
self.viewA.backgroundColor = [UIColor orangeColor];
self.viewA.frame = CGRectMake(0, 100, 320, 100);
[self.view addSubview:self.viewA];
}
#end
On pressing the button, you'll see the view jump about 50 pixels (half the distance it moves).
If anyone know why this is happening and/or how to address this issue, I would love to hear from you. Thank you in advance,
Patrick

You can use the UIViewAnimationOptionBeginFromCurrentState option to avoid the jump:
[UIView animateWithDuration:5.0 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
self.viewA.frame = CGRectMake(0, 200, 320, self.viewA.frame.size.height);
} completion:nil];

That's how blocks work, you are doing both animations at the same time, try this.
[UIView animateWithDuration:5 animations:^{
self.viewA.frame = CGRectMake(0, self.viewA.frame.origin.y, 320, 200);
}
completion:^(BOOL finished) {
self.viewA.frame = CGRectMake(0, 200, 320, self.viewA.frame.size.height);
}
];

Related

Why doesn't my animation loop?

I need to perform a blinking effect on a image once the user click on a particular button. But my code doesn't work:
- (void)next
{
[UIView animateWithDuration:2.0f delay:0 options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat animations:^{
NSLog(#"Done"); // <- USED AS COUNTER
[image setAlpha:0];
[image setAlpha:0.5];
} completion:nil];
}
What happens is my image changing its alpha to 0.5 and stops. Furthermore my console shows DONE just one time. What did I wrong?
I just created a test project and this blinked just fine...
#import "ViewController.h"
#interface ViewController ()
#property (nonatomic, strong)UIView *blinker;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.blinker = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
self.blinker.backgroundColor = [UIColor greenColor];
[self.view addSubview:self.blinker];
[self next];
}
- (void)next
{
[UIView animateWithDuration:2.0f delay:0 options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat animations:^{
NSLog(#"Done"); // <- USED AS COUNTER
[self.blinker setAlpha:0];
[self.blinker setAlpha:0.5];
} completion:nil];
}
With the exception that you expect the log to continue to print out in the console. So I suspect you are doing something to your image. You aren't calling self. so I am guessing it is an instance variable (ivar) and you are changing what it points to somewhere else in the code.
If you need a counter to go off every blink you may want to consider something like this...
#import "ViewController.h"
#interface ViewController ()
#property (nonatomic, strong)UIView *blinker;
#property (nonatomic,)NSInteger counter;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.blinker = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
self.blinker.backgroundColor = [UIColor greenColor];
[self.view addSubview:self.blinker];
[self next];
}
- (void)next
{
[UIView animateWithDuration:1.0 animations:^{
self.counter++;
NSLog(#"Counter: %#", #(self.counter));
self.blinker.alpha = 0.0;
} completion:^(BOOL finished) {
[UIView animateWithDuration:1.0 animations:^{
self.blinker.alpha = 1.0;
} completion:^(BOOL finished) {
//check to see if you want to continue blinking if so call next again
[self next];
}];
}];
}
I don't have any context as to why you want to count, but hopefully that helps.

How can I move a UIView to an end location but make it slow down as it approaches it?

I'm trying to move a box from point A = (0, 0) to B = (0, 1000) but I want to slow down as it approaches point B. I tried UIViewAnimationOptionCurveEaseOut but it seems to be moving at a constant velocity.
UIView *box = [[UIView alloc] init];
box.frame = CGRectMake(0, 0, 500, 500);
box.backgroundColor = [UIColor redColor];
[self.view addSubview:box];
// Animation
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
box.frame = CGRectMake(0, 1000, box.frame.size.width, box.frame.size.height);
} completion:nil];
Any ideas?
The easiest solution is to do the animation in multiple stages, with different speed, e.g. like this:
// Animation stage 1
[UIView animateWithDuration:0.1 //First half is fast
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
box.frame = CGRectMake(0, 500, box.frame.size.width, box.frame.size.height);
} completion:^(BOOL finished) {
// Animation stage 2
[UIView animateWithDuration:0.2 //Second half slower
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
box.frame = CGRectMake(0, 1000, box.frame.size.width, box.frame.size.height);
} completion:nil];
}];
It should be possible to get a smooth animation by playing around with it a bit.
If this is iOS 7+, use spring animations. Add this code to viewDidAppear: to test it out and see the difference between using a spring vs. a simple ease out curve.
Also note that Apple really encourages the use of spring animations going forward, since almost all (or all?) of their animations in iOS 7+ are done with springs.
UIView *fooView = [[UIView alloc] initWithFrame:CGRectMake(50, 100, 50, 50)];
fooView.backgroundColor = [UIColor redColor];
[self.view addSubview:fooView];
[UIView animateWithDuration:1
delay:1
usingSpringWithDamping:1
initialSpringVelocity:1
options:0
animations:^{
fooView.frame = CGRectMake(50, 400, 50, 50);
}
completion:nil];
UIView *barView = [[UIView alloc] initWithFrame:CGRectMake(200, 100, 50, 50)];
barView.backgroundColor = [UIColor redColor];
[self.view addSubview:barView];
[UIView animateWithDuration:1
delay:1
options:UIViewAnimationOptionCurveEaseOut
animations:^{
barView.frame = CGRectMake(200, 400, 50, 50);
}
completion:nil];
If you're not satisfied with built-in easing functions you can create your own. You want something related to CAMediaTimerFunction or CAKeyframeAnimation. You can read about it here and here. There's also some good answers here.

How to animate a UIImageview to display fullscreen by tapping on it?

I have an UIImageView in a UITableviewCell. When it is tapped, the UIImageView should animated to be displayed fullscreen. When the image is tapped when it is fullscreen it should shrink back to the original position.
How can this be achieved?
Add a gesture recognizer to the view controller.
Add the gesture Recognizer to your header file
#interface viewController : UIViewController <UIGestureRecognizerDelegate>{
UITapGestureRecognizer *tap;
BOOL isFullScreen;
CGRect prevFrame;
}
In your viewDidLoad add this:
isFullScreen = false;
tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imgToFullScreen)];
tap.delegate = self;
Add the following delegatemethod:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
{
BOOL shouldReceiveTouch = YES;
if (gestureRecognizer == tap) {
shouldReceiveTouch = (touch.view == yourImageView);
}
return shouldReceiveTouch;
}
Now you just need to implement your imgToFullScreen method.
Make sure you work with the isFullScreen Bool (fullscreen if it is false and back to old size if it's true)
The imgToFullScreen method depends on how you want to make the image become fullscreen.
One way would be:
(this is untested but should work)
-(void)imgToFullScreen{
if (!isFullScreen) {
[UIView animateWithDuration:0.5 delay:0 options:0 animations:^{
//save previous frame
prevFrame = yourImageView.frame;
[yourImageView setFrame:[[UIScreen mainScreen] bounds]];
}completion:^(BOOL finished){
isFullScreen = true;
}];
return;
} else {
[UIView animateWithDuration:0.5 delay:0 options:0 animations:^{
[yourImageView setFrame:prevFrame];
}completion:^(BOOL finished){
isFullScreen = false;
}];
return;
}
}
The code from #AzzUrr1, small error corrections (brackets) and tapper implemented slightly different.
Worked for me. Now it would be great to have this implemented with a scrollView, that the user can zoom in/out if the picture is bigger.. Any suggestion?
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UIGestureRecognizerDelegate>{
UITapGestureRecognizer *tap;
BOOL isFullScreen;
CGRect prevFrame;
}
#property (nonatomic, strong) UIImageView *imageView;
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
isFullScreen = FALSE;
tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imgToFullScreen)];
tap.delegate = self;
self.view.backgroundColor = [UIColor purpleColor];
_imageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 10, 300, 200)];
_imageView.contentMode = UIViewContentModeScaleAspectFill;
[_imageView setClipsToBounds:YES];
_imageView.userInteractionEnabled = YES;
_imageView.image = [UIImage imageNamed:#"Muppetshow-2.png"];
UITapGestureRecognizer *tapper = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imgToFullScreen:)];
tapper.numberOfTapsRequired = 1;
[_imageView addGestureRecognizer:tapper];
[self.view addSubview:_imageView];
}
-(void)imgToFullScreen:(UITapGestureRecognizer*)sender {
if (!isFullScreen) {
[UIView animateWithDuration:0.5 delay:0 options:0 animations:^{
//save previous frame
prevFrame = _imageView.frame;
[_imageView setFrame:[[UIScreen mainScreen] bounds]];
}completion:^(BOOL finished){
isFullScreen = TRUE;
}];
return;
}
else{
[UIView animateWithDuration:0.5 delay:0 options:0 animations:^{
[_imageView setFrame:prevFrame];
}completion:^(BOOL finished){
isFullScreen = FALSE;
}];
return;
}
}
I ended up using MHFacebookImageViewer. Integration is easy, no subclassing UIImageView, and it also has image zooming and flick dismiss.
Although it requires AFNetworking (for loading larger image from URL), you can comment out some code (about 10 lines) to remove this dependency. I can post my AFNetworking-free version if someone needs it. Let me know :)
One possible implementation would be to use a modal view controller with UIModalPresentationFullScreen presentation style.
Just finished a version in swift, just download and add into your project:
GSSimpleImageView.swift
And usage:
let imageView = GSSimpleImageView(frame: CGRectMake(20, 100, 200, 200))
imageView.image = UIImage(named: "test2.png")
self.view.addSubview(imageView)

How to flip an individual UIView (without flipping the parent view)

This is an iPad project where I have a UIView with several subViews, and I am trying to animate one of this UIViews using [UIView transitionFromView:toView:duration:options:completion], but when I run this method the whole parent view gets flipped!
This is the code I'm using:
UIView *secondView = [[UIView alloc] initWithFrame:CGRectMake(200, 200, 300, 300)];
[newView setBackgroundColor:[UIColor redColor]];
[UIView transitionFromView:self.firstView.view toView:secondView duration:1.0 options:UIViewAnimationOptionTransitionFlipFromLeft completion:nil];
Any idea on how can I flip between this views without animating the whole parent view?
Thanks!
this code may helps you:
put the two views you want to flip inside an unnamed view with the same size
and link the IBOutlet UIView *newView,*oldView; to the views and put the new view on top
bool a = NO;
#implementation ViewController
- (IBAction)flip:(id)sender
{
if (a == NO) {
[UIView transitionFromView:oldView toView:newView
duration:1.0
options:UIViewAnimationOptionTransitionFlipFromLeft
completion:NULL];
a = YES; // a = !a;
}
else {
[UIView transitionFromView:newView toView:oldView
duration:1.0
options:UIViewAnimationOptionTransitionFlipFromLeft
completion:NULL];
a = NO; // a = !a;
}
}
I had problems getting this to work also. I used the code written by Floris, but although it worked for the first "flip" the next flip started to go weird where the buttons and labels on the frontside UI View lost there positions.
I put the code below into place and it is working fine.
Couple of things to note:
panelView is a UIView control on the ViewController that has two other UIViews nested inside it (subviews). The first one is called frontView, second one is called backView. (see picture below)
I have a bool property called displayingFront on the class
(in my .h)
#property BOOL displayingFront;
in my .m
#synthesize displayingFront;
in my viewDidLoad method
self.displayingFront = YES;
This is the code in the .m that i have rigged up to the two buttons if front and back UIViews...
- (IBAction)flip:(id)sender
{
[UIView transitionWithView:self.panelView
duration:1.0
options:(displayingFront ? UIViewAnimationOptionTransitionFlipFromRight :
UIViewAnimationOptionTransitionFlipFromLeft)
animations: ^{
if(displayingFront)
{
self.frontView.hidden = true;
self.backView.hidden = false;
}
else
{
self.frontView.hidden = false;
self.backView.hidden = true;
}
}
completion:^(BOOL finished) {
if (finished) {
displayingFront = !displayingFront;
}
}];
}
Use a container view to hold your subviews, and make the container view the same size as your subview that you're trying to animate.
So, you can setup your views like this.
self.view-> "a container view" -> subviews
**//flipping view continuously**
bool a = NO;
-(void)viewDidLoad
{
// THIS is the canvass holding the two views this view is flipping
UIView *v=[[UIView alloc]initWithFrame:CGRectMake(40, 40, 150, 150)];
v.backgroundColor=[UIColor clearColor];
[self.view addSubview:v];
[v addSubview:yourfirstview];
[v addSubview:yoursecondview];
// calling the method
[NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(flip)
userInfo:nil
repeats:YES];
}
//flipping view randomly
-(void)flip
{
if (a == NO)
{
[UIView transitionFromView:yourfirstview toView:yoursecondview duration:1.0 options:UIViewAnimationOptionTransitionFlipFromLeft completion:NULL];
a = YES;
}
else if(a==YES)
{
[UIView transitionFromView:yoursecondview toView:yourfirstview duration:1.0 options:UIViewAnimationOptionTransitionFlipFromLeft completion:NULL];
a = NO;
}
}
I fixed this problem by using old-school animations:
[UIView beginAnimations:#"Flip" context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:firstView cache:YES];
[self addSubview:secondView];
[UIView commitAnimations];
Additionally you can remove the firstView:
[firstView removeFromSuperview];
I'd like to make an update of Steve Parker's answer in SWIFT 4:-
var displayBlankView:Bool = true
UIView.transition(with:flipView, duration:1.0, options:displayBlankView ? .transitionFlipFromLeft:.transitionFlipFromRight, animations:{
if(self.displayBlankView){
self.view1.isHidden=true
self.view2.isHidden=false
}else{
self.view1.isHidden=false
self.view2.isHidden=true
}
}, completion:{
(finished: Bool) in
self.displayBlankView = !self.displayBlankView;
})

Animating the frame of a UISegmentedControl

I have a UISegmentedControl that I'm trying to animate the frame of using the following (simplified, but functional) code:
UISegmentedControl *seg = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:#"item1", #"item2", nil]];
[seg setFrame:CGRectMake(0, 300, 100, 50)];
[self addSubview:seg];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^ {
[UIView animateWithDuration:5.0 animations: ^ {
[seg setFrame:CGRectMake(100, 0, 400, 50)];
}];
});
What happens is the size changes immediately, but the position animates properly. Why is it doing this, and are there any workarounds?
Tell the segmented control to layout if needed in the animation block.
[UIView animateWithDuration:.25f animations:^{
self.segmentedControl.frame = frame;
[self.segmentedControl layoutIfNeeded];
}];
It seems that you can work around this by manually resizing all the subviews of the UISegmentedControl inside the animation block.