Where should AutoLayout code be placed? - ios

When I'm doing the initial setup of a UIView's subviews, if I initialise a UIView:
//Listing A
UIView *redBox = [[UIView alloc] init];
[redBox setBackgroundColor:[UIColor redColor]];
[self.view addSubview:redBox];
[redBox setTranslatesAutoresizingMaskIntoConstraints:NO];
And I want to programmatically apply Auto Layout constraints to it:
//Listing B
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[redBox(100)]" options:0 metrics:nil views:#{#"redBox": redBox}];
[self.view addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[redBox(100)]" options:0 metrics:nil views:#{#"redBox": redBox}];
[self.view addConstraints:constraints];
Where should Listing B go?
Should it always immediately follow Listing A?
Or, if we're in a UIView subclass, should it go into:
updateConstraints
initWithFrame or
layoutSubviews ?
Similarly, if we're in a UIViewController subclass, and I place Listing A in viewDidLoad, should Listing B immediately follow it or should it go into:
initWithNibName:bundle:
viewWillLayoutSubviews or
updateViewConstraints ?

I often put it all in a method (or series of methods) called setUpConstraints or something like this.
Then I call that method from viewDidLoad.
If you're doing it in a UIView then I call my setupConstraints method from the init methods and awakeFromNib.
Having said that the method updateViewConstraints is already provided for you for this purpose so you could call your setupConstraints method from there.

viewDidLoad is the best place to apply the custom layout because it will not conflict with xib constraint at initialisation!

Related

autolayout problems by code?

i use autolayout without sb and xib.But i have some problems recently.
i don't understand which view should i use to implement the following two methods
- (void)addConstraint:(NSLayoutConstraint *)constraint
- (void)addConstraints:(NSArray<__kindof NSLayoutConstraint *> *)constraints
for example,i have a super view as following:
_menuView = [[UIScrollView alloc] init];
_menuView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:_menuView];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[_menuView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_menuView)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[_menuView(40)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_menuView)]];
and two subViews:view1,view2.When i use autolayout to add constraints to describe view1 and view2,
[WHICHVIEW addConstraint:<#(nonnull NSLayoutConstraint *)#>];
or
[WHICHVIEW addConstraints:<#(nonnull NSArray<__kindof NSLayoutConstraint *> *)#>];
what should WHICHVIEW should be?
Let me put it another way,is WHICHVIEW depend on the relationship between view1 and view2?what if view1 is not the same hierarchy as view2?
WHICHVIEW should be a parent of [all] the items you are trying to constrain. So if you are adding a number of views to a scrollview and trying to constrain them, with respect to themselves and to the scrollview, the scrollview should be WHICHVIEW. If view1 and view2 are not at all in the same hierarchy, then you cannot add a constraint between the two.
Do not use any WHICHVIEW. Do not call addConstraints:. Call NSLayoutConstraint.activateConstraints instead. It has the advantage that it does all that work for you - it adds the constraints to the correct views, automatically.

Fit CustomView into SuperView with Autolayout

I'm trying to create a card view that flips based on this tutorial. It creates a UIView and adds the card view.
For this I created a custom UIView (with Xib) and so far it works fine. I've added the correct constraints in my Storyboard for the view on which addSubview is called. This is working so far, but when I add the custom UIView it refers to its size in the xib and not the size of the superview.
How can I add the necessary Autolayout constraints to make the subview fit into its superview?
Thanks!
P.S. I've written it in Objective-C but it doesn't matter to me, if the answer is in swift or Objective-C.
Not sure, what wrong is there. I did the same thing in following way:
1) ProductItemView, a sub class of UIView and created ProductItemView.xib which is having a UIView object.
2) In .xib set File's owner class to ProductItemView so that UIView object of .xib and other subviews can be loaded and linked with IBOutlet.(see image)
3) In init method (initWithFrame: in my case) method put below code
NSArray *nibViewArray = [[NSBundle mainBundle] loadNibNamed:#"ProductItemView" owner:self options:nil];
if (nibViewArray.count) {
UIView *nibView = [nibViewArray objectAtIndex:0];
[self addSubview:nibView];
nibView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewDict = NSDictionaryOfVariableBindings(nibView);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[nibView]-0-|" options:0 metrics:nil views:viewDict]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[nibView]-0-|" options:0 metrics:nil views:viewDict]];
}
and finally create object of ProdutItemView and with frame or set Constraints and add it to super view. If you are setting constraint then do not forget to set translatesAutoresizingMaskIntoConstraints property to NO.
Hope it will be helpful for you.
if you want to do it in code. seems to work out better for me and makes more sense.
// make sure myCustomView is added to the superview
// and ignore the layout guides if the superview is not your view controller
[myCustomView setTranslatesAutoresizingMaskIntoConstraints: NO];
id topGuide = self.topLayoutGuide;
id bottomGuide = self.bottomLayoutGuide;
NSDictionary * viewsDictionary = NSDictionaryOfVariableBindings(myCustomView, topGuide, bottomGuide);
[mySuperiew addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[myCustomView]|" options:0 metrics: 0 views:viewsDictionary]];
[mySuperiew addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[topGuide][myCustomView][bottomGuide]|" options:0 metrics: 0 views:viewsDictionary]];

AutoLayout two labels within a TableViewCell?

I'm fairly new to iOS programming and probably don't understand the view hierarchy as well as I should and thus am failing to successfully get two labels within a custom table cell class I have created to autoresize properly. Namely the "translatesAutoresizingMaskIntoConstraints" property has me a little confused.
I am not using storyboards for this part of the code: I have a TableViewController where I create my own tableView in viewDidLoad. In cellForRowAtIndexPath I init my own TableViewCell implementation.
The problem I'm having is that when I set "setTranslatesAutoresizingMaskIntoConstraints" to NO for the table view and the UILabels I create then add my constraints, I get the following error:
"Terminating app due to uncaught exception `'NSInternalInconsistencyException',` reason: 'Auto Layout still required after executing `-layoutSubviews`. UITableView's implementation of `-layoutSubviews` needs to call super.'"
If I comment out the setTranslatesAutoresizingMaskIntoConstraints lines, my app runs however I get the following warning about the constraints:
"Unable to simultaneously satisfy constraints. Probably at least one
of the constraints in the following list is one you don't want. Try
this: (1) look at each constraint and try to figure out which you
don't expect; (2) find the code that added the unwanted constraint or
constraints and fix it. (Note: If you're seeing
NSAutoresizingMaskLayoutConstraints that you don't understand, refer
to the documentation for the UIView property
translatesAutoresizingMaskIntoConstraints)"
Essentially what I want to do is to enter code here have the two labels flush against each other and for them to resize based on orientation/device (I will be setting a background colour on them so want them to look 'continuous')
Can anyone help me out and explain what I am missing? Thanks in advance.
My code for adding the labels is:
self.nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200.0f, 30.0f)];
self.nameLabel.textColor = [UIColor redColor];
self.nameLabel.font = [UIFont fontWithName:#"Helvetica Neue" size:12.0f];
self.nameLabel.backgroundColor = [UIColor brownColor];
[self.nameLabel setText:#"Test"];
// [self.nameLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.contentView addSubview:self.nameLabel];
...
NSDictionary *viewsDictionary =
NSDictionaryOfVariableBindings(nameLabel, summaryLabel);
NSArray *constraints =
[NSLayoutConstraint constraintsWithVisualFormat:#"|-[nameLabel][summaryLabel]-|"
options:0
metrics:nil
views:viewsDictionary];
I'm fairly new to ios programming and probably don't understand the
view hierarchy as well as I should and thus am failing to successfully
get two labels within a custom table cell class I have created to
autoresize properly. Namely the
"setTranslatesAutoresizingMaskIntoConstraints" property has me a
little confused.
Well, translatesAutoresizingMaskIntoConstraints is a property that is created by Apple to make the transition from Autoresizing (Spring and Struts) to Autolayout easier. Say, you had some AutoresizingMasks for your view and you just switched Autolayout ON without setting any constraints. Then your existing AutoresizingMasks will get converted into constraints which will hold the view in place. So, by default translatesAutoresizingMaskIntoConstraints property is set to be YES. However, when you start adding constraints, in 90% cases they will conflict with the constraints that got created by converting your AutoresizingMasks. So, it is better to turn it off by setting view.translatesAutoresizingMaskIntoConstraints = NO
In your code, the following might have been creating the problems:
The setting of Frame
You should not set frames to objects which you will be adding constraints to. It's a paradigm shift. When you think in Autolayout way, frames are but effects of setting right constraints who combinedly determine the frame of the view in question.
So, please remove the frame setting.
self.nameLabel = [[UILabel alloc] init]; will suffice.
Setting proper constraints
Your Code:
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(nameLabel, summaryLabel);
NSArray *constraints =
[NSLayoutConstraint constraintsWithVisualFormat:#"|-[nameLabel][summaryLabel]-|"
options:0
metrics:nil
views:viewsDictionary];
NameLabel
Now, the above constraints tells us the nameLabel should be horizontally (as you have not mentioned H: or V:) spaced "standard" distance (20px) from container, adjacent to the summaryLabel.
But what about its Y position and Width and Height?
So we need more constraints.
summaryLabel
Same is applicable for summaryLabel.
So, lets define them properly:
NSDictionary *viewsDictionary =
NSDictionaryOfVariableBindings(nameLabel, summaryLabel);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[nameLabel(100)][summaryLabel]-|" options:0 metrics:nil views:viewsDictionary];
NSArray *constraints1 = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[nameLabel(30)]" options:0 metrics:nil views:viewsDictionary];
NSArray *constraints2 = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[summaryLabel]" options:0 metrics:nil views:viewsDictionary];
NSArray *constraints3 = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[nameLabel(==summaryLabel)]" options:0 metrics:nil views:viewsDictionary];
[self.view addConstraints:constraints];
[self.view addConstraints:constraints1];
[self.view addConstraints:constraints2];
[self.view addConstraints:constraints3];
Now your views will look fine.
At any point of time, to check if your views are missing any constraints, pause the debugger and type the following in the console
po [[UIWindow keyWindow] _autolayoutTrace]
it will show which of your views are having AMBIGUOUS LAYOUT
Also, remember in Storyboard/IB if the constraints are showing as "orange" colour, you need more constraints to define the objects position. Once you have added all necessary constraints, the constraints colours turn to "blue"
First, are you adding constraints to self.contentView after creation?
Second, maybe your constraint set is insufficient for autolayout and it creates own constraints based on autoresizing mask. Try to add vertical constraints and width constraints for labels.

AutoLayout constraints for a autoresizing view created in ViewController's loadView

My UIViewController creates its view by overwriting the loadView method:
- (void)loadView {
UIView *view = [[UIView alloc] init];
view.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
self.view = view;
}
Now I'd like to switch to AutoLayout and therefore add an
view.translatesAutoresizingMaskIntoConstraints = NO;
to the loadView method. Now I have to specify the same constraints which were autogenerated before. My approach was to overwrite updateViewConstraints with
- (void)updateViewConstraints {
if (0 == [[self.view constraints] count]) {
NSDictionary* views = #{#"view" : self.view};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view]|" options:0 metrics:0 views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[view]|" options:0 metrics:0 views:views]];
}
[super updateViewConstraints];
}
But I get an exception because I think this kind of constraints should go with the super view:
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to install constraint on view. Does the constraint reference something from outside the subtree of the view? That's illegal.
So, how do the correct Contraints have to look like?
You need to set the constraints on the superview. The exception is caused by referencing the superview by passing "|" in the visual format. If you update your code like the following it will work:
- (void)updateViewConstraints {
if (self.view.superview != nil && [[self.view.superview constraints] count] == 0) {
NSDictionary* views = #{#"view" : self.view};
[self.view.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view]|" options:0 metrics:0 views:views]];
[self.view.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[view]|" options:0 metrics:0 views:views]];
}
[super updateViewConstraints];
}
In practice you'll probably want to check for something other than 0 constraints on the superview but this should help.
You don't have to set the constraints on your root view as Matt Neuburg explains the Chapter 19 of his Programming iOS 6 book, in section Manual Layout:
We have not bothered to give our view (self.view) a reasonable frame. This is because we are relying on someone else to frame the view appropriately. In this case, the “someone else” is the window, which responds to having its rootViewController property set to a view controller by framing the view controller’s view appropriately as the root view before putting it into the window as a subview.
The problem with CEarwood's approach is that this is a ViewController, and its view is not the subview of any other view, so calling self.view.subview just results in nil. Remember that the Apple documentation and guidelines strongly suggest that a UIViewController occupies more or less the whole screen (besides the navigation bar or tab bar etc.).
Palimondo's answer is basically the right one: your UIViewController needs to init its view in loadView, but it doesn't need to specify its frame or constraints because those are automatically set to the window's frame and constraints. This is exactly what is done by default if you don't implement loadView yourself.
I'm not sure you need to set the constraints for the root view of the window.
That said, your constraints look correct, I think the exception you get is because this:
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view]|" options:0 metrics:0 views:views]];
uses the | notation to represent the view's superview. As the root level view, it has no superview. Something like this may work better:
- (void)loadView {
UIView *customView = [[UIView alloc] init];
[self.view addSubview:customView];
NSDictionary* views = #{#"customView" : customView};
[customView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[customView]|" options:0 metrics:0 views:views]];
[customView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[customView]|" options:0 metrics:0 views:views]];
}

Constraints based layout not animated when the status bar height changes on iOS

I have this view that used to have autoresizingMask = UIViewAutoresizingFlexibleHeight
When the status bar would animate its height (like when hanging up a phone call), the view's height would animate and increase.
But with auto layout I'm replacing this autoresizingMask with constraints:
UIView *orangeView = [[UIView alloc] initWithFrame:CGRectZero];
orangeView.translatesAutoresizingMaskIntoConstraints = NO;
orangeView.backgroundColor = [UIColor orangeColor];
[self.view addSubview:orangeView];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[orangeView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(orangeView)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(40)-[orangeView]-(190)-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(orangeView)]];
But now, the change in my layout is not animated with the status bar, it's just changed without any animations.
Now I know that I should call -layoutIfNeeded in an animation block when using constraints-based layout. But here I'm not the one creating the animation block! So is there a way to animate the change?
Does it mean I have to found a place in my code that would be executed during this animation block I didn't initiate? I tried to set [self.view layoutIfNeeded] in my controller when the UIApplicationWillChangeStatusBarFrameNotificationis fired, but it doesn't work.
Make sure you add your constraints in the updateConstraints method.
Here's what the docs say:
Custom views that set up constraints themselves should do so by overriding this method. When your custom view notes that a change has been made to the view that invalidates one of its constraints, it should immediately remove that constraint, and then call setNeedsUpdateConstraints to note that constraints need to be updated.

Resources