I have a UISegmentedControl that is shown/hide like a modal window. Initially it does not have a selected segment. In IB, the Value Changed event is wired to the method - (IBAction)cardClassificationChanged:(UISegmentedControl *)sender. Here is that method:
- (IBAction)cardClassificationChanged:(UISegmentedControl *)sender
{
NSLog(#"%d", sender.selectedSegmentIndex);
// block for updating the categorization of current card asynchronously
[self.cardActionSheet hideWithAnimation];
}
If I comment out the last line (the call to -hideWithAnimation), the selection changes as expected, everything works. However, with the call to that animation method, the UISegmentedControl selection will not visually change before the animation. Here is the hideWithAnimation method:
- (void)hideWithAnimation
{
CATransition *animation = [CATransition animation];
animation.type = kCATransitionFade;
animation.duration = globalAnimationLength;
[self.layer addAnimation:animation forKey:nil];
self.hidden = YES;
}
The next time this view appears (from a touch gesture), the UISegmentedControl will have the correct segment selected though.
It seems like I should not have to call setNeedsDisplay for the UISegmentedControl, but even when I experiment with it in the cardClassificationChanged method or the hideWithAnimation method, it does not refresh.
I'm obviously missing something related to the UI update, what do I need to call to update the UISegmentedControl selection before the animation?
I suggest you should use UIKit animations for fading out your control. Try the following code
- (void)hideWithAnimation
{
[UIView animateWithDuration:0.2
animations:^{
self.alpha = 0.;
} completion:^(BOOL finished) {
self.alpha = 1.;
self.hidden = YES;
}];
}
Related
I have two animations which are overlapping, which, because of the way I've set my method up, causes the second one not to fire. I have a check like so in the beginning of the method:
- (void)animateHidden:(BOOL)hidden duration:(CGFloat)seconds delay:(CGFloat)delay options:(UIViewAnimationOptions)options disableUserInteraction:(BOOL)disableUserInteraction {
if (self.hidden == hidden) {
return;
}
Then, further down, my animation block looks like so:
__weak UIView *weakSelf = self;
[UIView animateWithDuration:seconds delay:delay options:options animations:^{
weakSelf.alpha = hidden ? 0 : 1;
} completion:^(BOOL finished) {
// Return user interaction to previous state
if (disableUserInteraction) {
weakSelf.userInteractionEnabled = userInteractionEnabled;
}
weakSelf.hidden = hidden;
}];
Two animations are kicked off on the same view, one before a service call and one after. If the service call happens quick enough that the view is still animating, weakSelf.hidden = hidden; will never be called, and the second animation will exit out since the hidden value wasn't updated in time.
Is there anyway that I could force the completion block on the animation block to be called? I need to update my hidden property before making the check, but can't find a way to accomplish this.
Calling [self.layer removeAllAnimations] doesn't seem to work unfortunately.
You can use CABasicAnimation instead of UIView animation, that will solve the problem more accurately.
You can use it like:
CABasicAnimation* opacityZero= [CABasicAnimation animationWithKeyPath:#"opacity"];
[opacityZero setToValue:[NSNumber numberWithFloat:0.0]];
[opacityZero setDuration:duration];
[[self layer] addAnimation:opacityZero forKey:#"opacityZero"];
And when your service call ends, you can call [self.layer removeAllAnimations];
Similarly, you can make the opacity one and tweak the above method as you like.
You can find more info here.
A __block prefixed to hidden attribute declaration should help.
Something like, #property (nonatomic) __block BOOL hidden;
If you are targeting iOS 10+ take a look at UIViewPropertyAnimator
https://developer.apple.com/reference/uikit/uiviewpropertyanimator?language=objc
Combined with the UIViewAnimating and UIViewImplicitlyAnimating protocols, this allows modification / interruption / pause / resume / stop / etc of the animations.
Basic example (buttons and view set in IB):
- (IBAction)startTapped:(id)sender {
_myAnimator = [UIViewPropertyAnimator
runningPropertyAnimatorWithDuration:3.0
delay:0.0
options:UIViewAnimationOptionCurveLinear
animations:^{
_theRedBox.alpha = _theRedBox.alpha > 0 ? 0 : 1;
} completion:^(UIViewAnimatingPosition finalPosition) {
// do stuff
}];
}
- (IBAction)stopTapped:(id)sender {
[_myAnimator stopAnimation:NO];
[_myAnimator finishAnimationAtPosition:UIViewAnimatingPositionEnd];
}
I have an iOS application which runs a few different UIViewAnimation blocks to animate a few different objects on screen. This all works, but how can I stop ONE of the animation blocks WITHOUT stopping the rest?
I have tried using the following method:
[button.layer removeAllAnimations];
But it doesn't do anything, the animation just continues.
I then tried to use a simple BOOL value and get the animation to return from the method once the BOOL is set to "NO", but that doesn't work either.
Here is the animation which I am trying to stop:
-(void)undo_animation:(int)num {
// Fade out/in the number button label
// animation which lets the user know
// they can undo this particular action.
[UIView animateWithDuration:0.5 delay:0.2 options:(UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAllowUserInteraction) animations:^{
// Mostly fade out the number button label.
((UIButton *)_buttons[num]).alpha = 0.2;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.5 delay:0.2 options:(UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAllowUserInteraction) animations:^{
// Fade in the number button label.
((UIButton *)_buttons[num]).alpha = 1.0;
} completion:^(BOOL finished) {
// Stop the animation if the BOOL
// is set to 'NO' animations.
if (anim_state_button == NO) {
return;
}
}];
}];
}
Thanks, Dan.
You can't reach running animation property if you're using UIKit Animations. So I suggest to using Core Animation if you want to modify animation in the runtime of it.
And it's too simple to removing alpha of the view like below.
CABasicAnimation* fadein= [CABasicAnimation animationWithKeyPath:#"opacity"];
[fadein setToValue:[NSNumber numberWithFloat:1.0]];
[fadein setDuration:0.5];
[[moviepic layer]addAnimation:fadein forKey:#"MyAnimation"];
after adding animation to layer animation will start and than you can use delegate methods to be informed about animationDidFinish: method
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
NSLog(#"Animation interrupted: %#", (!flag)?#"Yes" : #"No");
}
Also you can reach whenever you want from using;
[[moviepic layer] animationForKey:#"MyAnimation"];
And of course you need to add CoreAnimation Framework to your project.
Hope it helps.
I think the simple way is that remove all animations from your View Layer as all the animation are by default added into the View's Layer.
[yourRequiredView.layer removeAllAnimations];
My understanding is that removing all animations from the relevant layer should stop all animations. How about [((UIButton *)_buttons[num]).layer removeAllAnimations]; ?
I have an application that first displays a login screen. Once the user logs in, this code is executed:
[UIView transitionWithView:self.view.window
duration:0.7
options:UIViewAnimationOptionTransitionNone
animations:^{
self.view.window.rootViewController = menuController;
} completion:^(BOOL finished) {
// Code to run after animation
}];
This works as expected; however, I would like to define my own custom animation option instead of the flip from left/right option.
A simple example would be sliding the new view controller onto the screen from top to bottom. How can I do this when changing the rootViewController?
Also, is there a completely different approach to animate switching the rootViewController? I am not married to this solution, it is just the closest thing I can find through Google/SO search.
Any help would be great, thanks!
Using a CATransition should be able to give you the sort of animation you're looking for. Granted, I've never actually attempted this specifically with rootViewController, but to slide the view in from top to bottom, I'd suggest trying this:
CATransition *transition = [CATransition animation];
transition.duration = 1.0;
transition.type = kCATransitionFromBottom;
transition.subtype = kCATransitionFade;
CALayer *layer = self.view.window.rootViewController.view.layer;
[layer addAnimation:transition forKey:kCATransition];
For that change to happen you must first add the ViewController and its view to the parent viewController. A simple trick could be add it "on the roof" of your window (starting Y position = -self.view.bounds.size.height). And then use
[UIView animateWithDuration:
delay:
options:
animations:^{}
completion:^(BOOL finished){
self.view.window.rootViewController = menuController;
}];
Then simply inside the animation brackets create the animation you want.
I have a feeling I'm overlooking something elementary, but what better way to find it than to be wrong on the internet?
I have a fairly basic UI. The view for my UIViewController is a subclass whose +layerClass is CAGradientLayer. Depending on the user's actions, I need to move some UI elements around, and change the values of the background's gradient. The code looks something like this:
[UIView animateWithDuration:0.3 animations:^{
self.subview1.frame = CGRectMake(...);
self.subview2.frame = CGRectMake(...);
self.subview2.alpha = 0;
NSArray* newColors = [NSArray arrayWithObjects:
(id)firstColor.CGColor,
(id)secondColor.CGColor,
nil];
[(CAGradientLayer *)self.layer setColors:newColors];
}];
The issue is that the changes I make in this block to the subviews animate just fine (stuff moves and fades), but the change to the gradient's colors does not. It just swaps.
Now, the documentation does say that Core Animation code within an animation block won't inherit the block's properties (duration, easing, etc.). But is it the case that that doesn't define an animation transaction at all? (The implication of the docs seems to be that you'll get a default animation, where I get none.)
Do I have to use explicit CAAnimation to make this work? (And if so, why?)
There seem to be two things going on here. The first (as Travis correctly points out, and the documentation states) is that UIKit animations don't seem to hold any sway over the implicit animation applied to CALayer property changes. I think this is weird (UIKit must be using Core Animation), but it is what it is.
Here's a (possibly very dumb?) workaround for that problem:
NSTimeInterval duration = 2.0; // slow things down for ease of debugging
[UIView animateWithDuration:duration animations:^{
[CATransaction begin];
[CATransaction setAnimationDuration:duration];
// ... do stuff to things here ...
[CATransaction commit];
}];
The other key is that this gradient layer is my view's layer. That means that my view is the layer's delegate (where, if the gradient layer was just a sublayer, it wouldn't have a delegate). And the UIView implementation of -actionForLayer:forKey: returns NSNull for the "colors" event. (Probably every event that isn't on a specific list of UIView animations.)
Adding the following code to my view will cause the color change to be animated as expected:
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
id<CAAction> action = [super actionForLayer:layer forKey:event];
if( [#"colors" isEqualToString:event]
&& (nil == action || (id)[NSNull null] == action) ) {
action = [CABasicAnimation animationWithKeyPath:event];
}
return action;
}
You have to use explicit CAAnimations, because you're changing the value of a CALayer.
UIViewAnimations work on UIView properties, but not directly on their CALayer's properties...
Actually, you should use a CABasicAnimation so that you can access its fromValue and toValue properties.
The following code should work for you:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[UIView animateWithDuration:2.0f
delay:0.0f
options:UIViewAnimationCurveEaseInOut
animations:^{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"colors"];
animation.duration = 2.0f;
animation.delegate = self;
animation.fromValue = ((CAGradientLayer *)self.layer).colors;
animation.toValue = [NSArray arrayWithObjects:(id)[UIColor blackColor].CGColor,(id)[UIColor whiteColor].CGColor,nil];
[self.layer addAnimation:animation forKey:#"animateColors"];
}
completion:nil];
}
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
NSString *keyPath = ((CAPropertyAnimation *)anim).keyPath;
if ([keyPath isEqualToString:#"colors"]) {
((CAGradientLayer *)self.layer).colors = ((CABasicAnimation *)anim).toValue;
}
}
There is a trick with CAAnimations in that you HAVE to explicitly set the value of the property AFTER you complete the animation.
You do this by setting the delegate, in this case I set it to the object which calls the animation, and then override its animationDidStop:finished: method to include the setting of the CAGradientLayer's colors to their final value.
You'll also have to do a bit of casting in the animationDidStop: method, to access the properties of the animation.
i want to slide in a menu from a side in a View after a buttonclick event. After another Buttonclick the menu should be slided out from the big View...
I experimented with CATransition but i can't solve this problem...
CATransition *animation = [CATransition animation];
[animation setDuration:1];
[animation setType:kCATransitionPush];
[animation setSubtype:kCATransitionFromBottom];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]] ;
[[menuView layer] addAnimation:animation forKey:#"#asdf"];
[menuView setCenter:CGPointMake([menuView center].x, [menuView center].y + 450)];
it work a bit, but i did not understand why.. :-/
If you want to implement it yourself one way you can go about it is create a custom view and add a button click event for a button in your view controller.
Also create a boolean variable to identify whether your custom view is visible or not.
On click, if menu view is not visible, then do this:
-(void)viewDidLoad
{
...
// ---------------------------------------------
// create the custom menu view off screen
// ---------------------------------------------
menuView = [[MenuView alloc] initWithFrame:CGRectMake(-320, 0, 320, 480)];
menuView.alpha = 0; // hide it so it doesn't show at the start, only show when slide in
[self.view addSubview:menuView];
...
}
-(void)toggleMenu
{
if(menuIsVisible)
{
menuIsVisible = NO;
[self hideMenu];
}
else
{
menuIsVisible = YES;
[self showMenu];
}
}
-(void)hideMenu
{
[UIView animateWithDuration:0.5 animations:^{
// note CGAffineTransforms doesn't use coordinates, they use offset
// so an offset of (0,0) would bring the menuView back to the coordinate
// when it was first instantiated, in our case, (-320, 0).
menuView.transform = CGAffineTransformMakeTranslation(0,0);
} completion:^{
// hide the menu
menuView.alpha = 0;
}];
}
-(void)showMenu
{
// show the menu
menuView.alpha = 1;
[UIView animateWithDuration:0.5 animations:^{
// note CGAffineTransforms doesn't use coordinates, they use offset
// so an offset of (320,0) from coordinate (-320,0) would slide the
// menuView out into view
menuView.transform = CGAffineTransformMakeTranslation(320,0);
}];
}
A Controller like ViewDeck will enable you to do that.
Edit:
I'm not sure what else to add to this, moderator. It's open source code that will add a menu that is controlled by a button which will slide in when clicked and slide out when clicked again, similar to what Facebook and Path have. You're also gaining the touch events with slide.
There's an example of how to use it on the github page here: https://github.com/Inferis/ViewDeck#how-to-use-it and also several examples projects available in the source code.