Rotate UIImageView, then Reverse Rotation when 'Tapped' (Animation) - ios

So, I've been working for hours trying to fix this, and I think It might just be missing something so obvious.
What Im trying to do is have an animation of a line (like a clock second hand) rotating around clockwise, but when the screen is pressed, it stops, but then when pressed again it starts again but the rotation reverses and the line starts rotating anticlockwise...
This is the code I used to rotate the line about a fixed point, and it works:
- (void) spinWithOptions: (UIViewAnimationOptions) options {
[UIView animateWithDuration: 0.0f
delay: 0.0f
options: options
animations: ^{
Hand.layer.anchorPoint = CGPointMake(0.5,1);
Hand.transform = CGAffineTransformRotate(Hand.transform, M_PI / 40);
}
completion: ^(BOOL finished) {
if (finished) {
if (animating) {
// if flag still set, keep spinning with constant speed
[self spinWithOptions: UIViewAnimationOptionCurveLinear];
} else if (options != UIViewAnimationOptionCurveEaseOut) {
// one last spin, with deceleration
//[self spinWithOptions: UIViewAnimationOptionCurveEaseOut];
}
}
}];
}
- (void) startSpin {
if (!animating) {
animating = YES;
[self spinWithOptions: UIViewAnimationOptionCurveEaseIn];
Clockwise = NO;
}
}
- (void) spinWithOptions_Anti: (UIViewAnimationOptions) options {
[UIView animateWithDuration: 0.0f
delay: 0.0f
options: options
animations: ^{
Hand.layer.anchorPoint = CGPointMake(0.5,1);
Hand.transform = CGAffineTransformRotate(Hand.transform, -M_PI / 40);
}
completion: ^(BOOL finished) {
if (finished) {
if (animating) {
// if flag still set, keep spinning with constant speed
[self spinWithOptions: UIViewAnimationOptionCurveLinear];
} else if (options != UIViewAnimationOptionCurveEaseOut) {
// one last spin, with deceleration
//[self spinWithOptions: UIViewAnimationOptionCurveEaseOut];
}
}
}];
}
- (void) startSpin_Anti {
if (!animating) {
animating = YES;
[self spinWithOptions_Anti: UIViewAnimationOptionCurveEaseIn];
Clockwise = YES;
}
}
- (void) stopSpin {
animating = NO;
}
So at the start of the application, the line begins rotating clockwise, when the ViewDidload calls startSpin, which calls spinWithOptions...
Sure enough, if I press the screen, the rotation animation stops, with this...
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *) event{
NSLog(#"Dircetion: %i", Clockwise);
if(animating){
[self stopSpin];
}
else{
if(Clockwise){
[self startSpin];
NSLog(#"must be clockwise");
}
else{
[self startSpin_Anti];
NSLog(#"anti");
}
}
}
But when pressed again it starts rotating as It should but never changes direction... it only ever rotates clockwise...
I just can't seem to see why it won't change direction... even the NSLogs say the bool of Clockwise is correct...

Related

Smooth Animation before NSTimer, But now it's Glitchy

I have a UIImageView rotating forever on my screen... I use the code below, which I found HERE:
- (void) spinWithOptions: (UIViewAnimationOptions) options {
[UIView animateWithDuration: 0.0f
delay: 0.0f
options: options
animations: ^{
Hand.transform = CGAffineTransformRotate(Hand.transform, M_PI / 30);
}
completion: ^(BOOL finished) {
if (finished) {
if (animating) { //if, keep spinning
[self spinWithOptions: UIViewAnimationOptionCurveLinear];
} else if (options != UIViewAnimationOptionCurveEaseOut) { //final spin
//[self spinWithOptions: UIViewAnimationOptionCurveEaseOut];
}
}
}];
}
- (void) startSpin {
if (!animating) {
animating = YES;
[self spinWithOptions: UIViewAnimationOptionCurveEaseIn];
}
}
- (void) stopSpin {
animating = NO;
}
It worked find and the rotation seems smooth...
I've since added a countdown timer, using the follow:
-(void)timerTick:(NSTimer *)timer{
ticks -= 0.1; //NSLog(#"Total Ticks: %.2f",ticks);
if(ticks < 0.0){ //3 seconds is up
Failed.image = [UIImage imageNamed:#"Failed.png"];
ticks = 0.0;
[self GameOver]; //out of time...
}
else {
FailedBeforeTimer.image = [UIImage imageNamed:#"Failed Before.png"];
}
CountDownTimer.text = [NSString stringWithFormat:#"%.1f",ticks];
}
-(void)startTimer{
ticks = 3.0;
timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(timerTick:) userInfo:nil repeats:YES];
}
-(void)stopTimer{
[timer invalidate];
ticks = 0.0;
}
Since adding this timer to my app, the rotation animation is really blocky and no longer a smooth rotation...
Any ideas what I should implement to solve this?
To check, I removed the timer and re-run the application, and sure enough, the animation was smooth again :(
UPDATE:
After some more trial and error, it seems to be this line of code that slows the process down: CountDownTimer.text = [NSString stringWithFormat:#"%.1f",ticks]; ie. When I don't display the countDownTimer text on the screen/update label on screen), the rotation is smooth still...
So confused... What can I do?
Animating with zero-duration animations does not make sense. That's not how UIView animation is supposed to work. My guess is that is the source of your problems. A zero duration animation will be jumpy by definition.
You should be animating your changes over some time-period. (Always use a non-zero duration.)
EDIT:
Note that your timerTick method is horribly inefficient. You load the same image into an image view on every tick. Don't do that. Set it up with the initial image, and then only change the image when your tick count reaches zero.
You should give CADisplayLink a try, it should be suitable for your case. It's easy to use, there are heaps of tutorials out there, just do a search.

MKAnnotationView drag state ending animation

I'm using the custom MKAnnotationView animations provided by Daniel here: Subclassing MKAnnotationView and overriding setDragState but I run into an issue.
After the pin drop animation, when I go to move the map the mkannotationview jumps back to its previous location before the final pin drop animation block is called.
It seems to me that dragState=MKAnnotationViewDragStateEnding is being called before the animation runs? How can I get around this issue and set the final point of the mkannotationview to be the point it's at when the animation ends?
#import "MapPin.h"
NSString *const DPAnnotationViewDidFinishDrag = #"DPAnnotationViewDidFinishDrag";
NSString *const DPAnnotationViewKey = #"DPAnnotationView";
// Estimate a finger size
// This is the amount of pixels I consider
// that the finger will block when the user
// is dragging the pin.
// We will use this to lift the pin even higher during dragging
#define kFingerSize 20.0
#interface MapPin()
#property (nonatomic) CGPoint fingerPoint;
#end
#implementation MapPin
#synthesize dragState, fingerPoint, mapView;
- (void)setDragState:(MKAnnotationViewDragState)newDragState animated:(BOOL)animated
{
if(mapView){
id<MKMapViewDelegate> mapDelegate = (id<MKMapViewDelegate>)mapView.delegate;
[mapDelegate mapView:mapView annotationView:self didChangeDragState:newDragState fromOldState:dragState];
}
// Calculate how much to life the pin, so that it's over the finger, no under.
CGFloat liftValue = -(fingerPoint.y - self.frame.size.height - kFingerSize);
if (newDragState == MKAnnotationViewDragStateStarting)
{
CGPoint endPoint = CGPointMake(self.center.x,self.center.y-liftValue);
[MapPin animateWithDuration:0.2
animations:^{
self.center = endPoint;
}
completion:^(BOOL finished){
dragState = MKAnnotationViewDragStateDragging;
}];
}
else if (newDragState == MKAnnotationViewDragStateEnding)
{
// lift the pin again, and drop it to current placement with faster animation.
__block CGPoint endPoint = CGPointMake(self.center.x,self.center.y-liftValue);
[MapPin animateWithDuration:0.2
animations:^{
self.center = endPoint;
}
completion:^(BOOL finished){
endPoint = CGPointMake(self.center.x,self.center.y+liftValue);
[MapPin animateWithDuration:0.1
animations:^{
self.center = endPoint;
}
completion:^(BOOL finished){
dragState = MKAnnotationViewDragStateNone;
if(!mapView)
[[NSNotificationCenter defaultCenter] postNotificationName:DPAnnotationViewDidFinishDrag object:nil userInfo:[NSDictionary dictionaryWithObject:self.annotation forKey:DPAnnotationViewKey]];
}];
}];
}
else if (newDragState == MKAnnotationViewDragStateCanceling)
{
// drop the pin and set the state to none
CGPoint endPoint = CGPointMake(self.center.x,self.center.y+liftValue);
[UIView animateWithDuration:0.2
animations:^{
self.center = endPoint;
}
completion:^(BOOL finished){
dragState = MKAnnotationViewDragStateNone;
}];
}
}
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
// When the user touches the view, we need his point so we can calculate by how
// much we should life the annotation, this is so that we don't hide any part of
// the pin when the finger is down.
fingerPoint = point;
return [super hitTest:point withEvent:event];
}
#end
I had the same problem, especially under iOS 8. After many hours of testing I believe that iOS keeps track of where it thinks self.center of the annotation is during the time that the state is MKAnnotationViewDragStateDragging. You need to use extreme caution if you animate self.center when handling MKAnnotationViewDragStateEnding. Read that as "I couldn't get that to work, ever."
Instead, I kept Daniel's original code when handling states MKAnnotationViewDragStateStarting and MKAnnotationViewDragStateCanceling, I animated self.center. When handling MKAnnotationViewDragStateEnding I animated self.transform instead of self.center. This maintains the actual location of the annotation and just changes how it is rendered.
This works well for me running either iOS 7.1 and iOS 8.0. Also fixed a bug in hitTest, and added some code to reselect the annotation after dragging or canceling. I think that is the default behavior of MKPinAnnotationView.
- (void)setDragState:(MKAnnotationViewDragState)newDragState animated:(BOOL)animated
{
if(mapView){
id<MKMapViewDelegate> mapDelegate = (id<MKMapViewDelegate>)mapView.delegate;
[mapDelegate mapView:mapView annotationView:self didChangeDragState:newDragState fromOldState:dragState];
}
// Calculate how much to lift the pin, so that it's over the finger, not under.
CGFloat liftValue = -(fingerPoint.y - self.frame.size.height - kFingerSize);
if (newDragState == MKAnnotationViewDragStateStarting)
{
CGPoint endPoint = CGPointMake(self.center.x,self.center.y-liftValue);
[UIView animateWithDuration:0.2
animations:^{
self.center = endPoint;
}
completion:^(BOOL finished){
dragState = MKAnnotationViewDragStateDragging;
}];
}
else if (newDragState == MKAnnotationViewDragStateEnding)
{
CGAffineTransform theTransform = CGAffineTransformMakeTranslation(0, -liftValue);
[UIView animateWithDuration:0.2
animations:^{
self.transform = theTransform;
}
completion:^(BOOL finished){
CGAffineTransform theTransform2 = CGAffineTransformMakeTranslation(0, 0);
[UIView animateWithDuration:0.2
animations:^{
self.transform = theTransform2;
}
completion:^(BOOL finished){
dragState = MKAnnotationViewDragStateNone;
if(!mapView)
[[NSNotificationCenter defaultCenter] postNotificationName:DPAnnotationViewDidFinishDrag object:nil userInfo:[NSDictionary dictionaryWithObject:self.annotation forKey:DPAnnotationViewKey]];
// Added this to select the annotation after dragging.
// This is the behavior for MKPinAnnotationView
if (mapView)
[mapView selectAnnotation:self.annotation animated:YES];
}];
}];
}
else if (newDragState == MKAnnotationViewDragStateCanceling)
{
// drop the pin and set the state to none
CGPoint endPoint = CGPointMake(self.center.x,self.center.y+liftValue);
[UIView animateWithDuration:0.2
animations:^{
self.center = endPoint;
}
completion:^(BOOL finished){
dragState = MKAnnotationViewDragStateNone;
// Added this to select the annotation after canceling.
// This is the behavior for MKPinAnnotationView
if (mapView)
[mapView selectAnnotation:self.annotation animated:YES];
}];
}
}
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
// When the user touches the view, we need his point so we can calculate by how
// much we should life the annotation, this is so that we don't hide any part of
// the pin when the finger is down.
// Fixed a bug here. If a touch happened while the annotation view was being dragged
// then it screwed up the animation when the annotation was dropped.
if (dragState == MKAnnotationViewDragStateNone)
{
fingerPoint = point;
}
return [super hitTest:point withEvent:event];
}

NSTimer Movement irregular

I am trying to make a game movement animation in objective c so that when a button is pressed something will move across the screen while it is pressed. This works fine though the calling of the NSTimer which I am using is irregular and I am getting laggy movements. This is in iOS 6 so I wont use sprite kit as it is not available
Any suggestions on how to make it a regular call or to make it a smooth animation would be much appreciated
Thanks in advance
Here is the code:
-(void) click {
if (!self.animating) {
self.animating = YES;
timer = [NSTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:#selector(move)
userInfo:nil
repeats:YES];
}
}
- (void) cancelClick {
self.animating = NO;
[timer invalidate];
}
-(void) move {
CGPoint origin = self.target.frame.origin;
if ([self.direction isEqual:#"left"]) {
origin.x -= self.magnitude;
} else if ([self.direction isEqual:#"right"]) {
origin.x += _magnitude;
} else if ([self.direction isEqual:#"up"]) {
origin.y -= _magnitude;
} else {
origin.y += _magnitude;
}
[self.target move:0 toPoint:origin animated:YES delay:0 animationOptions:UIViewAnimationOptionCurveLinear];
}

UIView block animation not beginning from current state

I'm trying to animate a some subviews of a subview of an annotation view.
So, to be more clear, the gray pin is my annotation view, which has an invisible subview, and this subview contains those 4 colored circle views.
annotation view
| invisible view (called typeSelectionView)
| yellow view (called typeButton)
| blue view (called typeButton)
| green view (called typeButton)
| red view (called typeButton)
First the circles are not visible (transform scale (0, 0) and their center at the head of the gray pin), then, when I select the gray pin, those circles come from the pin head growing and moving to their original position (the one on the picture above).
When I deselect the pin, the opposite animation happens.
And when I select and deselect (or the opposite) before the animation is finished, it changes the way it was going (if it was growing it shrinks) reverting the animation in the middle. That is why I need the UIViewAnimationOptionBeginFromCurrentState.
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)annotationView
{
// in case the typeSelectionView was already being showed by another annotationView
if (self.typeSelectionView.superview != annotationView) {
[self.typeSelectionView removeFromSuperview];
[self.typeSelectionView showTypeButtons:NO animated:NO completion:NULL];
}
[annotationView addSubview:self.typeSelectionView];
[self.typeSelectionView showTypeButtons:YES animated:YES completion:NULL];
}
- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)annotationView
{
if (self.typeSelectionView.superview) {
[self.typeSelectionView showTypeButtons:NO
animated:YES
completion:^(BOOL finished) {
if (finished) {
[self.typeSelectionView removeFromSuperview];
}
}];
}
}
- (void)showTypeButtons:(BOOL)show animated:(BOOL)animated completion:(void (^)(BOOL))completion
{
void (^block)(void) = ^{
for (int i = 0; i < self.typeButtons.count; i++) {
TypeButton *typeButton = self.typeButtons[i];
if (show) {
typeButton.center = [self _typeButtonCenterForIndex:i numberOfButtons:self.typeButtons.count];
typeButton.transform = CGAffineTransformIdentity;
} else {
typeButton.center = CGPointMake(viewWidth / 2, viewheight + 5); // pin head center
typeButton.transform = CGAffineTransformMakeScale(0, 0);
}
}
};
if (animated) {
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:block
completion:^(BOOL finished) {
if (completion) {
completion(finished);
}
}];
} else {
block();
if (completion) {
completion(YES);
}
}
}
So far no problem, but when one gray pin is displaying the typeSelectionView (like in the picture above) and then I select another pin, it's like this part:
if (self.typeSelectionView.superview != annotationView) {
[self.typeSelectionView removeFromSuperview];
[self.typeSelectionView showTypeButtons:NO animated:NO completion:NULL];
}
did nothing.
The 4 circles simple jump from one pin to the other without any animation.
What I would expect is that the [self.typeSelectionView showTypeButtons:NO animated:NO completion:NULL]; set those buttons to be "hidden" (scale (0, 0) and center in the head of the pin) without any animation (because the invisible view doesn't have a superview anymore, so they are not even showing), and then when I add the typeSelectionView to the new annotationView with [annotationView addSubview:self.typeSelectionView];, I animate the circles to their original position again with [self.typeSelectionView showTypeButtons:YES animated:YES completion:NULL];.
Btw, doing [typeButton.layer removeAllAnimations] at the beginning of showTypeButtons:animated:completion: does nothing.
How do I solve this?
Thanks in advance.
EDIT: (BONUS if someone can explain that to me... and help me solve it) =)
Suppose the circles are showing like in the picture above, then this code:
NSLog(#"type selected");
[self.typeSelectionView showTypeButtons:NO
animated:YES
completion:^(BOOL finished) {
NSLog(#"finisheddddd: %d", finished);
}];
[self.typeSelectionView showTypeButtons:NO
animated:YES
completion:^(BOOL finished) {
NSLog(#"finished: %d", finished);
}];
generates the following output:
type selected
finished: 1
finisheddddd: 1
So the second animation finishes first and both animations finish with finished == YES.
Also the second animation (which finishes first) finishes instantly and not after the end of the animation time.
O.o
Check the code I wrote but not tested. Please experiment with it to get your result
- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)annotationView
{
if (self.typeSelectionView.superview)
{
[self.typeSelectionView showTypeButtons:NO
animated:YES
completion:^(BOOL finished) {
if (finished) {
[self.typeSelectionView removeFromSuperview];
}
}];
}
}
- (void)showTypeButtons:(BOOL)show animated:(BOOL)animated completion:(void (^) (BOOL))completion
{
[UIView animateWithDuration:(animated) ?0.5f : 0.0f
delay:(self.animationStarted)? 0.5 : 0.0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
self.animationStarted = YES;
[self setButtonVisible:show];
}
completion:^(BOOL finished) {
self.animationStarted = NO;
if (completion) {
completion(finished);
}
}];
}
-(void)setButtonVisible:(BOOL)visible
{
[self.typeButtons enumerateObjectsUsingBlock:^(TypeButton typeButton, NSUInteger idx, BOOL *stop) {
if (visible)
{
typeButton.center = [self _typeButtonCenterForIndex:idx numberOfButtons:self.typeButtons.count];
typeButton.transform = CGAffineTransformIdentity;
}
else
{
typeButton.center = CGPointMake(viewWidth / 2, viewheight + 5); // pin head center
typeButton.transform = CGAffineTransformMakeScale(0, 0);
}
}];
}
Turns out the problem is that when I set my typeButtons non-animated, they are not redraw instantly, so they don't change their current state, so what happens is:
one mapView:didSelectAnnotationView: method call:
- show the buttons animated
***redraw cycle here***
another mapView:didSelectAnnotationView: method call:
- hide the buttons non-animated
(no redraw cycle here, so buttons are still showing)
- show the buttons animated
So I came up with this shitty solution:
- (void)showTypeButtons:(BOOL)show animated:(BOOL)animated completion:(void (^)(BOOL))completion
{
void (^block)(void) = ^{
for (int i = 0; i < self.typeButtons.count; i++) {
TypeButton *typeButton = self.typeButtons[i];
if (show) {
typeButton.center = [self _typeButtonCenterForIndex:i numberOfButtons:self.typeButtons.count];
typeButton.transform = CGAffineTransformIdentity;
} else {
typeButton.center = CGPointMake(viewWidth / 2, viewheight + 5); // pin head center
typeButton.transform = CGAffineTransformMakeScale(0, 0);
}
}
};
if (animated) {
[UIView animateWithDuration:0.5
delay:0
options:self.typeButtonsChangedWithoutAnimation ? 0 : UIViewAnimationOptionBeginFromCurrentState
animations:block
completion:^(BOOL finished) {
if (completion) {
completion(finished);
}
}];
self.typeButtonsChangedWithoutAnimation = NO;
} else {
self.typeButtonsChangedWithoutAnimation = YES;
block();
if (completion) {
completion(YES);
}
}
}
Without the UIViewAnimationOptionBeginFromCurrentState, the animation starts from its actual state and not from its draw state.
One solution that I found, but did not work was: What is the most robust way to force a UIView to redraw?
Even if it did work, I wouldn't want it either...
Btw, I'm not gonna accept this answer, because it sucks.
It does not actually solve my question, which is to change the current state, not pass through it.
I'm just posting it in case someone sees and gets an idea of how to actually solve my original question.

How to add animation while changing the hidden mode of a uiview?

I want to add animation to a view while changing its hidden mode i.e
my_view.hidden=YES;
I have added a button in navigationbar. When we click on it the new view is set to be unhide. It draws at the upper of the navigation table.
Animate the view's opacity from 100% to 0%. Have the animation completion callback set the view to be hidden. You might also want to reset the opacity back to 100% during the callback, so the view will display fully opaque when you unhide it.
yourView.alpha = 0.0 //for zero opacity
yourView.alpha = 1.0 //for 100% opacity
There is no animation for hiding however; you get the same result with the Swift code below:
UIView.animate(withDuration: 0.2, delay: 0, options: [], animations: {
self.yourView.alpha = 0 // Here you can change the alpha property of the view
}, completion: { _ in
self.yourView.isHidden = true // Here you hide it when animation done
})
Unfortunately, hidden is not a property that is animatable through UIView animations. I think your best bet may be to use one of the animations #Erik B suggested, or start dabbling with Core Animations which are much more powerful. Take a glance at the documentation for UIView animations and Core Animations.
I achieved something like what your suggesting by using UIView animations to slide the new view from below another view. This made it appear like a drawer sliding out. If you want to do something like that, you need to intercept the touch up inside event and place the animation code there.
- (IBAction)buttonClicked:(id)sender {
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationCurveEaseOut
animations:^(void) {
self.myView.frame = /* set the frame here */
}
completion:NULL];
}
I think more appropriate way to do it is:
[UIView transitionWithView:aView
duration:0.3
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^(void){
aView.hidden = NO;
}
completion:nil];
Updated to Swift 3:
UIView.animate(withDuration: 0.2, delay: 0.2, options: .curveEaseOut,
animations: {firstView.alpha = 0},
completion: { _ in firstView.isHidden = true
//Do anything else that depends on this animation ending
})
And if you wish to animate something back after first view is gone, you can replicate the code inside the completion block with alpha = 1 and hidden = false.
Here's a category I wrote to introduce a new "hidden" property on UIView which correctly supports animation:
#implementation UIView (AnimateHidden)
-(void)setHiddenAnimated:(BOOL)hide
{
[UIView animateWithDuration:0.5
delay:0.0
options: UIViewAnimationCurveEaseOut
animations:^
{
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
if (hide)
self.alpha=0;
else
{
self.hidden= NO;
self.alpha=1;
}
}
completion:^(BOOL b)
{
if (hide)
self.hidden= YES;
}
];
}
#end
This is corrected N.J. version:
#implementation UIView (AnimateHidden)
-(void)setHiddenAnimated:(BOOL)hide duration:(NSTimeInterval)duration {
if(self.hidden == hide)
return;
if(hide)
self.alpha = 1;
else {
self.alpha = 0;
self.hidden = NO;
}
[UIView animateWithDuration:duration animations:^{
if (hide)
self.alpha = 0;
else
self.alpha = 1;
} completion:^(BOOL finished) {
if(finished)
self.hidden = hide;
}];
}
#end
Here is swift version for this :
Swift 2
UIView.animateWithDuration(0.5, delay: 0.2, options: UIViewAnimationOptions.CurveEaseOut, animations: {
objView.alpha = 0
}, completion: { finished in
objView.hidden = true
})
Swift 3, 4, 5
UIView.animate(withDuration: 0.5, delay: 0.2, options: UIView.AnimationOptions.curveEaseOut, animations: {
objView.alpha = 0
}, completion: { finished in
objView.isHidden = true
})
This performs animation with duration of 5 seconds and after delay of 2 seconds.
Available AnimationOptions are :
CurveEaseInOut, CurveEaseIn, CurveEaseOut, CurveLinear
Since a few of these answers are a bit cluttered I figured I could post my minimalistic design of this API. I also added the delay and duration - because why not.
In the implementation we have.
#import "UIView+AnimateHidden.h"
#implementation UIView (AnimateHidden)
- (void)setHiddenAnimated:(BOOL)hide
delay:(NSTimeInterval)delay
duration:(NSTimeInterval)duration {
[UIView animateWithDuration:duration
delay:delay
options:UIViewAnimationOptionAllowAnimatedContent
animations:^{
if (hide) {
self.alpha = 0;
} else {
self.alpha = 0;
self.hidden = NO; // We need this to see the animation 0 -> 1
self.alpha = 1;
}
} completion:^(BOOL finished) {
self.hidden = hide;
}];
}
#end
In the header file we have.
#import <UIKit/UIKit.h>
#interface UIView (AnimateHidden)
- (void)setHiddenAnimated:(BOOL)hide
delay:(NSTimeInterval)delay
duration:(NSTimeInterval)duration;
#end
NJ's and Stanislav's answers here helped me make a new category for this, which I think improves on their answers, so thought I'd post what I came up with in case it helps anyone else.
Note it will only work in iOS4 or later as it's using blocks.
UIView+AnimateHidden.m
#import "UIView+AnimateHidden.h"
#implementation UIView (AnimateHidden)
- (void)setHidden:(BOOL)hidden animated:(BOOL)animated
{
// If the hidden value is already set, do nothing
if (hidden == self.hidden) {
return;
}
// If no animation requested, do the normal setHidden method
else if (animated == NO) {
[self setHidden:hidden];
return;
}
else {
// Store the view's current alpha value
CGFloat origAlpha = self.alpha;
// If we're unhiding the view, make it invisible initially
if (hidden == NO) {
self.alpha = 0;
}
// Unhide the view so we can see the animation
self.hidden = NO;
// Do the animation
[UIView animateWithDuration:0.5
delay:0.0
options: UIViewAnimationOptionCurveEaseOut
animations:^{
// Start animation block
if (hidden == YES) {
self.alpha = 0;
}
else {
self.alpha = origAlpha;
}
// End animation block
}
completion:^(BOOL b){
// Start completion block
// Finish up by hiding the view if necessary...
self.hidden = hidden;
// ... and putting back the correct alpha value
self.alpha = origAlpha;
// End completion block
}];
}
}
#end
Another version if you wish to use more complex animation types or animations not supported by the UIView
- (void)setHidden:(BOOL)hidden withAnimationDuration:(NSTimeInterval)duration
{
CATransition* transition = ({
CATransition* its = [CATransition animation];
its.duration = duration;
its.timingFunction =
[CAMediaTimingFunction
functionWithName: kCAMediaTimingFunctionEaseInEaseOut];
its.type = kCATransitionPush;
its.subtype = (hidden ? #"fromBottom" : #"fromTop");
its
});
UIView* containerView = self.superview;
[containerView.layer removeAllAnimations];
[containerView.layer addAnimation: transition forKey: kCATransition];
self.hidden = hidden;
if (!hidden) {
[self.superview bringSubviewToFront: self];
}
}
Here is the code I used to model a view "growing" and "shrinking" on a "show more..." and "show less ..." button click. Modeled off the answer from Palyancodr
This approach allows me to create both views in the storyboard so that the constraints work as expected on the different iOS devices and I don't need to custom code all the constraints.
#IBAction func showMoreOrLessAction(_ sender: Any) {
// if small view showing
if showMoreLargeView.isHidden {
showMoreSmallView.isHidden = true
//showMoreLargeView.isHidden = false
UIView.animate(withDuration: 0.2, delay: 0, options: [], animations: {
self.showMoreLargeView.alpha = 1 // Here you will get the animation you want
}, completion: { _ in
self.showMoreLargeView.isHidden = false // Here you hide it when animation done
})
}
else { // large view showing
//showMoreSmallView.isHidden = false
UIView.animate(withDuration: 0.2, delay: 0, options: [], animations: {
self.showMoreSmallView.alpha = 1 // Here you will get the animation you want
}, completion: { _ in
self.showMoreSmallView.isHidden = false // Here you hide it when animation done
})
showMoreLargeView.isHidden = true
}
}

Resources