iOS: Auto Layout vs autoresizingMask - resizing whole view to fill? - ios

I've got a storyboard which is built using Auto Layout. Within that storyboard, I'm embedding a UIViewController subclass (ButtonGridViewController) in several locations, each of which is a different size. ButtonGridViewController's view is defined in a xib.
What I need is for the entirety of the ButtonGridViewController's view to simply scale-to-fill the view I'm embedding it in. With the old struts-and-springs method, this was trivial -- just set all the subviews to resize in both directions, and voila, piece of cake.
How do I accomplish the same thing using constraints? For what it's worth, the xib just contains a main view, which is rectangular, and has 4 subviews - each a button - arranged in a 2x2 grid. I want everything, including the buttons AND spacing, to scale and/or stretch to fill the view it's going into.
Thanks!

To accomplish the same thing using constraints you need to set the leading, trailing, top and bottom space to the superview to 0. See below:
//load the ButtonGridViewController from a xib
[[NSBundle mainBundle] loadNibNamed:#"..." owner:self options:nil];
//get the view add it
[self.view addSubView:self.myGridView];
//turn off springs and struts
[self.view setTranslatesAutoresizingMaskIntoConstraints:NO];
//add constraints to fill parent view
NSArray *arr;
//horizontal constraints
arr = [NSLayoutConstraint constraintsWithVisualFormat:#"|[vw]|"
options:0
metrics:nil
views:#{#"vw":self.myGridView}];
[self.view addConstraints:arr];
//vertical constraints
arr = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[vw]|"
options:0
metrics:nil
views:#{#"vw":self.myGridView}];
[self.view addConstraints:arr];

Related

Adding subview to UITableView with autolayout doesn't set its frame

I am using the following code to add a overlay view to a view:
UIView *overlayView = [[UIView alloc] init];
overlayView.backgroundColor = [UIColor redColor];
[self.view addSubview:overlayView];
overlayView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *views = NSDictionaryOfVariableBindings(overlayView);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[overlayView]|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[overlayView]|" options:0 metrics:nil views:views]];
If I place this code in the viewDidLoad method of a UIViewController it works fine. However, if I do the same in a UITableViewController the overlay view gets a zero frame.
I have inspected the view and I can se the constraints do get added correctly and they are active. But for som reason they seem to be ignored.
I don't get any error or warning.
What am I missing here?
PS: I know I can instantiate the overlay view with self.view.bounds as frame, and it works. However, I need to use autolayout.
Suspect it's because the UITableView is a UIScrollView and auto layout has a few caveats when working with views that establish their own bounds systems (such as the scroll view or table view).
The constraints set on the overlay view are such that it is constrained to the top, right, bottom, and left edges of the scroll view, and thus to the content area of the scroll view rather than the scroll view's frame.
See
https://developer.apple.com/library/ios/technotes/tn2154/_index.html
and Auto Layout Guide: Working with Scroll Views:
Constraints between the edges or margins of the scroll view and its
content attach to the scroll view’s content area.
Instead of constraining overlay view to the content area, constrain it to the scroll view's frame instead. According to the above references, this can be done either by constraining overlay view's width/height to the scroll view, or by constraining the overlay view to a view outside of the scroll view.

Superview not increasing in height based on the subviews constraint

I have a scrollview and a separate UIView where I placed a series of textFields and labels with constraints which fully occupies the top and bottom. I'm trying to adjust the UIView's height based on its subview constraints but it won't. What is happening is that the view keeps its height and force other textfields to collapse or shrink thus breaking the constraints.
Details
Each subview priority values :
compression = 750
hugging = 250
UIView priority values:
compression = 249
hugging = 749 Set to be lower than the rest.
Most of the textfields has aspect ratio constraint. This causes the field to adjust.
Each subview has vertical/top/bottom spacing between each other. The top and bottom elements has top and bottom constraints to the view as well.
What's on my code:
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
/* I had to adjust the UIView's width to fill the entire self.view.*/
if(![contentView isDescendantOfView:detailsScrollView]){
CGRect r = contentView.frame;
r.size.width = self.view.frame.size.width;
contentView.frame = r;
[detailsScrollView addSubview:contentView];
}
}
Screenshots
The view
This is what currently happens. In this instance it forces the email field to shrink. If I place a height value on it, it does not shrink but the layout engine finds another element to break
Edit:
Solved
Maybe I just needed some break to freshen up a bit. I did tried using constraints before but got no luck. However thanks to the suggestion I went back setting the constraints instead of setting the frame on this one and got it finally working.
Solution:
-(void)viewDidLoad{
[detailsScrollView addSubview:contentView];
[contentView setTranslatesAutoresizingMaskIntoConstraints:NO];
[detailsScrollView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(contentView,detailsScrollView);
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[contentView]-0-|"
options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil
views:viewsDictionary];
NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[contentView]-0-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:viewsDictionary];
NSArray *widthConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[contentView(==detailsScrollView)]-0-|" options:0 metrics:nil views:viewsDictionary];
}
When you use interface builder to deal with the UIScrollView and its child UIView. usually a top, bottom, left and equal width constraints are set between the UIScrollView and its child which is the contentView in your case.
Without those constraints the other option is to set the content size of the UIScrollView. which was the way of using the UIScrollView before introducing constraints.
So, 1. you should add those constraints programmatically.
By using the constraints, the views frame is no longer needed to resize the views.
So, 2. remove frame setting for your content view.
I am not so happy with the way you set the frame in the viewDidLayoutMethod. if I am going to do that here I would take the frame setting out of the if statement.
The code would be as follow with no if statement:
[detailsScrollView addSubview:contentView];
// then set the constraints here after adding the subview.
Put this code anywhere but not inside your viewDidLayoutSubviews method. it will be a bigger problem than setting the frame in there inside if statement.
Note: Originally, if you are going to set frame in the viewDidLayoutSubviews
method. you should do it for all cases. for example for the if case
and the else case. because, next time this method is going to be
called the views will respond to the constraint. and lose its frame.
Another observation: if you want the view to response to its subviews constraint why you need to set the frame for it? right?
After adding the constraint you may need to call the method constraintNeedsUpdate or another related method.

Can we load an XIB with auto layout as a subview of a view created programmatically without auto layout and have the subview resize with parent view?

Background:
Just started with learning and using auto layout. So I might be going wrong with constraints.
Supporting both iOS7 and iOS8, so haven't ventured into size classes.
Scenario:
I have a superview created programmatically which is present in all the screens of the app. Now I tried loading an XIB and assigned it as a subview to this superview. It looks fine in the iPhone (as the XIB was designed with iPhone dimensions). But in an iPad, using the same XIB, the subview is keeping the iPhone dimensions.
Is there any way this setup will work on an iPad, having the XIB resize to fill the screen without setting up a constraint between the subview and the superview?
I will post the existing constraints if you think it will help.
You can achieve it by either setting the frame of subview while adding it or adding constraints to subview programmatically.
Enjoy.. :)
you should add constraints for your view from xib after you create it programmatically. Think about it as a usual subview of your main view. so
Create your view from xib
UIView *someView = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([YOUR_VIEW_CLASS_NAME class]) owner:self options:nil] lastObject];
Add it as subview
[yourSuperview addSubview: someView];
Add constraints for top, bottom, left, right
NSDictionary *views = #{#"someView":self.background};
[yourSuperview addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|[someView]|"
options:0
metrics:nil
views:views]];
[yourSuperview addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[someView]|"
options:0
metrics:nil
views:views]];
Call update via [someView setNeedsLayout];

iOS Autolayout issue, Adding login view looks bad (image inside)

I'm adding a login view in my tableview. But as you can see on the image below it doesn't look great.
I am using auto layout for both the tableview and the loginview.
For the loginview I have the constraints showed on the image below, but what's strange(to me) is that these constraints seem to be valid for both the Green View and its superview (the black). Because, when I change it for one of them, it changes for the other as well.
Note: The loginview has separate a controller, but the controller is not presented. The loginview is just added to the tableview.
Any suggestions?
From what I can see in this image, there are 4(!) views you need to account for.
1.) the superview
2.) the UITableview
3.) the black view
4.) the login-view (blue-green?)
It looks like the constraints for (4) are set correctly in relation to (3)
To me, I'd examine the constraints for (3) in relation to (1).
In other words make sure the leading/trailing top/bottom constraints are all 0 or to margin in relation to the superview, and you should be good to go.
Here is a quick example on how you could implement this. This assumes you are using view controller containment, which you should be.
child is your signIn view and self is the Table view controller
// Add login view to my view
[self addChildViewController:child];
child.view.frame = self.view.bounds;
[self.view addSubview:child.view];
[child didMoveToParentViewController:self];
// Add constraints
NSDictionary * views = #{#"view" : child.view, #"super" : self.view};
NSArray * hConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[view]-|" options:0 metrics:nil views:views];
NSArray * vConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[view]-|" options:0 metrics:nil views:views];
[self.view addConstraints: [hConstraints arrayByAddingObjectsFromArray:vConstraints]];

iOS AutoLayout not really working for views embedded into ScrollView

I'd like to embed views into each other (in the current case into a scrollview to allow displaying taller content than the screen's size) and use iOS 6.0's AutoLayout feature in order to avoid constant calculation of content size's.
I have the following ViewController's view, containing a UIScrollView:
I'd like to display my ChildVC's view in this scrollview:
Please notice that the label is multiline and contains a 'lot of' text, also auto-layout constraints are defined (escpecially with a greater-than-or-equal to the height property).
I create the childVC and add it to the main VC's view via the following code:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
childVC = [[ChildVC alloc] initWithNibName:#"ChildVC" bundle:nil];
self.myScrollView.translatesAutoresizingMaskIntoConstraints = NO;
childVC.view.translatesAutoresizingMaskIntoConstraints = NO;
[self.myScrollView addSubview:childVC.view];
NSDictionary *viewsDictionary = #{ #"subView" : childVC.view};
NSString* constr = [NSString stringWithFormat:#"|-0-[subView(%f)]-0-|", self.myScrollView.frame.size.width];
[self.myScrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:constr options:0 metrics: 0 views:viewsDictionary]];
[self.myScrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[subView]-(>=0)-|" options:0 metrics: 0 views:viewsDictionary]];
}
The constraints for the content-view (which I guess will be the childVC's view) are the only stuff set up in code, because I only want a vertical scrollbar, I want the child-view's content to (horizontally) shrink to be displayed in the scrollviewer.
And my output is this:
The UI is displayed, shrinked but my label does not resize vertically therefore it does not display the whole text :(
I tried to set up translatesAutoresizingMaskIntoConstraints to the label here and there without any success.
I'd appreciate any help because I'm struggling with this for days now :/
Personally, I think those constraints and the auto layout feature just don't always work exactly as we want them to. (Especially on different screen sizes).
So I normally add a few line of code in the viewDidLoad method to move certain objects around by a few pixels. You can also change sizes and so on.
Literally a simple movement like this does the trick without the user noticing any animations:
object_name.frame = CGRectOffset(object_name.frame, 0, -40.0f);

Resources