Constraint animation issue - ios

I'm building an app whereas I'm facing a strange issue with constraints.
I'm trying to animate a height constraint attached to a UIView in storyboard, but the simulator displays some weird behaviour. To debug, I created a completely new, iOS 9.2 project and added a UIButton, set up a horizontal pin and superview top constraint to it, linked the top constraint to my header file and the button itself to an IBAction.
When calling this code alone:
- (IBAction)myAction:(id)sender {
self.topConstraint.constant += 150.0f;
}
The button snaps 150 points down, even without calling [self.view layoutIfNeeded];.
However, when I'm using this code:
- (IBAction)myAction:(id)sender {
self.topConstraint.constant += 150.0f;
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:1 animations:^{
[self.view layoutIfNeeded];
}];
}
The button slides down.
How come it snaps there without me calling layoutIfNeeded when there's no animation block? I find this very strange.
Here's my header file:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *topConstraint;
#end
And my top NSLayoutConstraint setup:
And the UIViewController in interface builder:

Calling layoutIfNeeded on a view merely forces an update of the view early, rather than waiting for an update through the drawing cycle, when you animate your constraint you are placing the layoutIfNeeded inside the animation block thus animating the change, in the 2nd instance where you do not call layoutIfNeeded, the drawing cycle comes around and updates the view without animation.

Related

iOS - Animations inside Navigation Bar doesn't work

I have a scenario where I needed to add an animation inside the Navigation Bar, but the simulator doesn't animate. I add a UIView, add a label/text field and create outlets for the constraints and update them with "UIView animateWithDuration:" Instead, the simulator just updates the constraints and displays the view accordingly, but without any animation. I have tried it by modifying both constraints as well as frames (separately).
[self.view layoutIfNeeded];
[UIView animateWithDuration: 3
delay: 0.0
options: UIViewAnimationOptionCurveEaseInOut
animations: ^{
self.constraint.constant += 60;
NSLog(#"inside animation");
[self.view layoutIfNeeded];
}
completion:nil];
This is shown when I increment the width constraint by "60" in viewDidLoad: . And it comes out non-animated.
When I configure the constraints (increment width by "60") through a button click, even a static updation doesn't happen.
The same code works perfectly outside the navigation bar inside the UIView, though.
Appreciate any help...
If your navigation bar contains some animation, then you could try out drawing custom navigation bar. Here's what you've to do :
In your view controller in viewDidLoad() method add the following code
self.navigationController.navigationBarHidden = YES;
After that using a UIView create a custom navigation bar with frame adjusted to be visible and top.
After that add components on the view and start working with animations.
You can achieve that by taking custom UIView and create width NSLayoutConstraint outlet to it and in code you can handle the constant property of this variable by calling UpdateConstraintsIfNeeded or UpdateViewConstraints method before calling animation code
Ok, I've solved this at last. Phew...
What I did was...
Added a view in place of barButtonItem (let's call this "superView") and over it another view (and let's call this "subView") so that I get the following configuration.
Constraints were set between subView and objects on top of it. Now, I created outlets (superView and subView) for superView and subView, made them transparent (optional, of course).
Useful note: Set the superView's width to the default bar button item's width and the subView's to the screen width. Disable "Clip Subviews" on superView but enable on the subView.
Added animations as needed where I had wanted, but layoutIfNeeded on the "superView", not the parent view.
[self.superView layoutIfNeeded];
Good luck.

Basic UIAnimation

This is a very basic question but its my first time doing any animation in an app.
I have the following scenario. View 1 with some text is displayed and at a certain time the other views 2 and 3 should be animated in from the top pushing down on the first view.
At the moment I'm using auto layout in storyboard. Should I stop doing that when I want to animate or continue using it?
Do I have to programmatically add/update constraints or show/hidden views?
I have tried show/hidden but it doesn't seem to work for me. When trying this I added all the views (right picture) and then set view 2 and 3 to be hidden hoping the bottom view to automatically go up, but that didn't work...
I haven't tried to manually set constraints because i have never done it before. I have a basic understanding of how it should be done but would like to know first if its the way to go?
I've set up a very basic project to illustrate my answer. You can find it here. Almost everything is done in IB. The left red view is pinned to the top of the screen and this constraint is connected to an IBOutlet in ViewController.m. Position of other views depend on position of the left red view. We change this constraint's constant value two times - first time after VC has loaded but not yet appeared on the screen, to move red views out of the screen. Second time, when button is pressed, we change constraint to what it was and force layout with animation. That's pretty much it.
#import "ViewController.h"
#interface ViewController ()
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *redViewTopConstraint;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.redViewTopConstraint.constant = - ([UIScreen mainScreen].bounds.size.width * 0.3 + 20);
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)action:(id)sender {
self.redViewTopConstraint.constant = 20;
[UIView animateWithDuration:0.5f delay:0 usingSpringWithDamping:0.65f initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
#end

iOS8 issue - UIView drawRect is not called

I have a very simple UIView, that is only drawing a triangle. It implements a UIView drawRect method that is drawing a figure. It is working fine on iOS7, but when I run the same code on iOS8 it is not even called. Any ideas what has changed on iOS8? My code in nutshell:
#interface MyView : UIView
#end
#implementation MyView
- (instancetype)init {
self = [super init];
if (self) {
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (void)drawRect:(CGRect)rect {
// draw things
}
#end
myView = [MyView new];
[self addSubview:myView];
Update
My view hierarchy: UIViewController View->Custom UILabel->MyView
It is an error message bubble, and my view is a beak pointing to something.
I have used a new UI debugging tool, but it is not visible there. Init method is called, drawRect is not. Something must have changed in iOS8, because iOS7 is calling drawRect.
Update 2
Of course I'm using constraints (and Masonry pod), and this is why I did not specify the frame.
[myView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(make.superview.mas_bottom);
make.centerX.equalTo(make.superview);
make.width.equalTo(#10.0f);
make.height.equalTo(#5.0f);
}];
I have also tried adding [myView setNeedsDisplay] in various places, but it didn't help.
Problem finally solved. I'm not sure what exactly caused the issue (Masonry [constraints framework] or iOS8 changes to UILabel), but the solution was to change the view hierarchy. I created another UIView that contains both UILabel and my UIView (drawn beak) instead of adding the UIView to UILabel as subview. Now drawRect method is called both on iOS7 and iOS8.
Previous hierarchy:
UIViewController View->Custom UILabel->MyView
New hierarchy:
UIViewController View->Container UIView->Custom UILabel & MyView
To sum up, if your drawRect method is not called on iOS8, and you are adding some UIView to UILabel as subview, try to use some container which will contain both UIView and UILabel.
make sure that myView's superview property clipToBounds = NO, and that myView rect is on screen
There is an important detail missing in your approach. You need to specify a frame that determines the origin and size of your view. This can be done with the initWithRect method of UIView, or you can set the frame after allocation/initialization. Since you have a custom init method already I would do the following:
myView = [MyView new];
myView.frame = CGRectMake(0.0,0.0,100.0,100.0);
[self addSubview:myView];
This will draw your custom view with an origin point of (0.0,0.0) and a width and height of 100.0.
Furthermore, adding a call to setNeedsDisplay should force the drawRect method to be called if you are still having trouble:
[myView setNeedsDisplay];

UIScrollView not working with Autolayout called from a Tab Bar Controller

I have created a very simple test environment one Tab Bar Controller and one View Controller with the following structure:
UI TAB BAR Controller ---------> UIScrollView
UIScrollView
View
Scroll View
Label
Label
Label
.h file
#interface rpViewController : UIViewController
#property (strong, nonatomic) IBOutlet UIScrollView *scrollView;
#end
.m file
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.scrollView.contentSize = CGSizeMake(320, 1000);
}
If the start entry point for the app is the view with the scroller everything works fine!
If the start entry point is theTab Bar Controller the scroller is not working?
If your going to work purely with Autolayout you should not be doing any direct manipulations of frames, bounds or contentSize. Apple has written a technical note about working with UIScrollView and Autolayout Technical Note TN2154 that you should read.
So to answer the question, if you are going to be using auto layout you cannot manipulate contentSize and expect consistent results.

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];
}];

Resources