iOS: Animate subview without calling layoutSubviews - ios

In layoutSubviews i layout my child views into appropriate positions, and for some of them apply some transformation. Now i want to make on this subviews repeatable scale animation to draw attention. But, when i launch animation with [UIView animate....], with changing transform property - fired layoutSubviews method, which conflict with animation (it override transform and animation not played).
Is there any good way (without bool flags) to handle this behavior?
Example:
- (void)layoutSubviews {
[super layoutSubviews];
myChildView.transform = CGAffineTransformMake....;
}
- (void)animate {
CGAffineTransform originalTransform = myChildView.transform;
[UIView animateWithDuration:0.1f
delay:0.0f
options:UIViewAnimationOptionAutoreverse
animations:^{
[UIView setAnimationRepeatCount:4];
//next line of code produce calling layoutSubviews
myChildView.transform = CGAffineTransformMakeScale(1.15f, 1.15f);
} completion:^(BOOL finished){
myChildView.transform = originalTransform;
//after this also called layoutSubviews
}];
}

Recently, I have encountered this issue. I override the method viewDidLayoutSubviews inside the view controller to control the animation.
- (void)viewDidLayoutSubviews {
// This method will be called after subview's layoutSubviews.
if (self.shouldAnimated) {
// Do animation at here.
}
}
However, this is just a dirty solution.

I suggest you would do better to not use layoutSubviews. Instead create and position and transform your subviews either inside the init method or a custom method called from the init.

I meet this problem also. And I add another view(called subView) as a subview, and add the myChildView to subView. This will not trigger the layoutSubviews of superview when I change the transform of myChildView. And why, you can see this UIView/CALayer: Transform triggers layoutSubviews in superview.

Is there any good way (without bool flags) to handle this behavior?
Yes. Rid off "layoutSubviews" method and set transform for children views in another method.
[self.parentView setUpChildrenViews];
...
[self.parentView animate];

Related

iOS - Cancel constraints animation

I'm animating constraints the usual way:
centerXsettingsBtnConstraint.constant = aValue;
[UIView animateWithDuration:.5
animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
isAnimating = NO;}];
When I'm rotating the device I want to cancel this animation and update constraint with new values.
I tried to call
[self.view.layer removeAllAnimations];
but the animation still goes on till the rotation of the device is over. Only then I can update with new constraints, but during the rotation it's all a mess on the screen.
Is there a way to cancel constraints animation?
You must call the remove.. method on each subview:
[self.view.layer removeAllAnimations];
I'll use Swift:
for eachView in self.view.subviews {
eachView.layer.removeAllAnimations()
}
You need to remove animation on the specified view whose constraint or property is being changed in that particular animation.
Call removeAllAnimations() on the view on whom centerXsettingsBtnConstraint is applied.
You do not need to call removeAllAnimations() on each subview.
If you are not able to find the particular view, try to print someView.layer.animationKeys(), you will find the view on which animation is applied.
For Swift:
This will work to stop the animation of the constraint
yourComponent.layer.removeAnimation(forKey: "position")

Object returns to the original position (after an animation) when keyboard appears

I have this strange new issue: this code (that works perfectly)
[UIView animateWithDuration:0.4 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
CGRect frame = _logoClaim.frame;
frame.origin.y -= 180;
_logoClaim.frame = frame;
} completion:NULL];
moves a view that contains a UIImageView and an UILabel to the top of my self.view.
The view that moves unhides a UITextField.
When I try to write text into the UITextField, obviously appear the keyboard.
And at this moment, the view animated before, returns to the original start position!!!
What is the reason?
Put a completion block in your animation and check the finished value. The keyboard is cancelling the animation and the finished bool will be NO. I would disable input in the UITextField until the animation is completed. Do this in your completion block.
EDIT
Looking at your duration and re-reading the question,I may be mistaken with what I think you mean. If this answer is incorrect I will remove it.
Also, search your code for _logoClaim.frame in case you are adjusting it onKeyboardWillAppear
You would need to create outlet for its bottom/top space constraints and update the constant value of it inside the animation block.
[UIView animateWithDuration:0.4
...
animations:^{
/*Update the value of the constraint outlet supposing it to be a bottom space constraint*/
_layoutConstraintBottom += 180;
[self.logoClaim layoutIfNeeded];
} completion:NULL];
Do not forget to call -layoutIfNeeded for your animating view.

Why does viewDidLayoutSubviews interfere with animations?

I have a view controller where the views are laid out manually (no xib). This is done in viewDidLayoutSubviews. I want to animate one of the views, but the animation doesn't work (no observable motion of the view) because viewDidLayoutSubviews appears to be called immediately as the animation begins.
Relevant portions of code:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
logo.frame = CGRectMake(0, 0, 640, 360);
}
- (void)shrink
{
[UIView animateWithDuration:0.5 animations:^{logo.frame = CGRectMake(0, 0, 320, 180);}];
}
The shrink method is called when another timer fires, so it's not called straight away on view controller creation.
If I set up logo.frame elsewhere and remove that line from viewDidLayoutSubviews, then the animation works correctly.
What is the explanation for this behaviour, and what is a recommended way to work around it?
Have you tried to call animation method in viewDidApper ? Instead of use viewDidLayoutSubviews try to use viewDidLoad

UIView's frame gets overridden by Storyboard on initial load

I need to change the width of a subview depending on, say, available disk space. So I have a selector that's called upon the view controller's viewWillLayoutSubviews as such:
CGRect rect = self.usageView.bounds;
rect.size.width = self.someCalculatedwidth;
[self.usageView setFrame:rect];
[self.usageView setNeedsDisplay];
Really, I just want to change the width of the subview. It works if the view controller is the initial controller; however, if it is transitioned from, say, a Push Segue it stopped working. What happens is I'd see my desired width rendered for moment but then it gets changed to the original width as per its blueprint on the Storyboard.
The behavior feels like the iOS caches the original (storyboard) view in a block during the push segue animation, and upon completion it executes the block which doesn't have my calculations above; thereby overridden the desired view. Any idea?
There are two approaches.
The first approach is to circumvent the problem, with a side benefit of cool animation. Since I have no clue when or how many times or exactly how iOS enforces the auto-constraints, I simply delay my UIView modifications and smooth it out with animation. So I wrap view animation around the code in my selector:
[UIView animateWithDuration:.25 animations:^{
CGRect rect = self.usageView.bounds;
rect.size.width = self.someCalculatedwidth;
[self.usageView setFrame:rect];
[self.usageView setNeedsDisplay];
}];
Then I'd call my selector in my controller's viewDidAppear:animated:
[self performSelector:#selector(resetUsageView) withObject:self afterDelay:0.0];
Now most would say this is a cop-out or a kludge. And that's fair.
In the second approach I attack the root of the problem - auto layout/constraints. I add a Width constraint to my usageView in the storyboard, and connect its IBOutlet to my code.
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *usageViewWidthConstraint;
Then I modify the constraint as per the calculated width in my controller's viewWillAppear:animated:
[self.usageViewWidthConstraint setConstant:self.someCalculatedWidth];
Voila! A one liner.
Now what if I want animation given I got a taste of it from the first approach? Well, constraint changes are outside the realm of animation so basically I'd need a combination of the first and second approaches. I change the UIView's frame for animation, and modify the constraint as well to "set the record straight":
[UIView animateWithDuration:.25 animations:^{
CGRect rect = self.usageView.bounds;
rect.size.width = self.someCalculatedwidth;
[self.usageView setFrame:rect];
[self.usageViewWidthConstraint setConstant:self.someCalculatedWidth]; // <<<<
[self.usageView setNeedsDisplay];
}];

iOS - Trying to fade a view controller in

In fading a view controller in from black, I am doing the following within viewDidLoad:
Creating a UIView with a black background;
Giving the UIView an alpha value of 1.0f;
Adding the UIView as a subview of [self view];
Fading the black UIView out via animateWithDuration by changing its alpha value to 0.0f; and
Removing the black UIView from [[self view] subviews]
More often than not, this works as planned. Occasionally, however, I see a glimpse of the view controller I want initially hidden, just before the black UIView is drawn.
Is there a way to avoid this? Is there a better method to place this code in than viewDidLoad?
Many thanks
Yes, add the view in the loadView method and do the actual animation in viewDidLoad or viewDidAppear. Or do as the above commentor said and simply use the view alpha.
I would create the UIView that I want hidden in UIViewController's nib file, then link that to via an IBOutlet
#interface SomeViewController: UIViewController
{
IBOutlet UIView *blackView;
}
then in UIViewController's -(void) viewDidLoad; method, I would do the following
- (void)viewDidLoad
{
[super viewDidLoad];
// Fade the opacity of blackView over 1 second,
// then remove it from the view controller.
[UIView animateWithDuration:1
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
blackView.layer.opacity = 0;
}
completion:^(BOOL finished) {
// This line prevents the flash
blackView.layer.opacity = 0;
[blackView removeFromSuperview];
}];
}

Resources