Achieving this layout for a viewcontroller using autolayout - ios8

I am working on a viewcontroller and I would like to try to achieve something like the picture below. I'd like to do this so it looks great on any device with regards to aspect ratio.
The top is a container, the middle is a collectionview, and the bottom is a uitableview.
What i'm trying to preserve is the aspect ratios. My thought to do this was the following:
For the first box, set the leading, trailing, and top margins to be to the container (guideline). Set the bottom one to be the box below (the larger middle box). Set the aspect ratio as well.
For the middle box, set the leading/trailing margins to the guidelines, and set the bottom to the box below. Also set the aspect ratio.
For the last box, set the leading, trailing, bottom (to the guideline) and also the aspect ratio.
I also set to pin widths equally
After doing this, it preserves my ratios correctly but it throws a ton of errors and warnings. Any ideas as to why this would be cranky at me? The crashing/warning report:
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)
(
"<NSLayoutConstraint:0x7f8a66031bc0 V:[UITableView:0x7f8a65837c00(73)]>",
"<NSLayoutConstraint:0x7f8a6605c150 UITableView:0x7f8a65837c00.width == 7.78082*UITableView:0x7f8a65837c00.height>",
"<NSLayoutConstraint:0x7f8a6604e970 UICollectionView:0x7f8a65838400.leading == UIView:0x7f8a66031eb0.leadingMargin>",
"<NSLayoutConstraint:0x7f8a6604e9c0 UICollectionView:0x7f8a65838400.trailing == UIView:0x7f8a66031eb0.trailingMargin>",
"<NSLayoutConstraint:0x7f8a6604ea10 UICollectionView:0x7f8a65838400.width == UITableView:0x7f8a65837c00.width>",
"<NSLayoutConstraint:0x7f8a63c4ccf0 'UIView-Encapsulated-Layout-Width' H:[UIView:0x7f8a66031eb0(320)]>"
)
Thanks so much!

Say, you want top view's height to be 20% of the main view and middle view's height to be 50% of the main view. You can do this programatically like this:
[topView setTranslatesAutoresizingMaskIntoConstraints: NO];
[middleView setTranslatesAutoresizingMaskIntoConstraints: NO];
[bottomView setTranslatesAutoresizingMaskIntoConstraints: NO];
NSDictionary *views = #{#"topView": topView, #"middleView": middleView, #"bottomView": bottomView};
[self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat: #"H:|[topView]|" options: 0 metrics: nil views: views]];
[self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat: #"H:|[middleView]|" options: 0 metrics: nil views: views]];
[self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat: #"H:|[bottomView]|" options: 0 metrics: nil views: views]];
[self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat: #"V:|[topView][middleView][bottomView]|" options: 0 metrics: nil views: views]];
[self.view addConstraint: [NSLayoutConstraint constraintWithItem: topView attribute: NSLayoutAttributeHeight relatedBy: NSLayoutRelationEqual toItem: self.view attribute: NSLayoutAttributeHeight multiplier: 0.2f constant: 0.0f]];
[self.view addConstraint: [NSLayoutConstraint constraintWithItem: middleView attribute: NSLayoutAttributeHeight relatedBy: NSLayoutRelationEqual toItem: self.view attribute: NSLayoutAttributeHeight multiplier: 0.5f constant: 0.0f]];
You need not set aspect height for the bottom view. You only need to pin the bottom view with the bottom edge of the main view.
If you want to do in Interface Builder, you can do this way:
For the top box, add 'Leading', 'Trailing' and 'Top' constraints to the superview. Also, add 'Equal Heights' constraint to the superview and modify the multiplier to the required value (Refer the last image).
For the middle box, add 'Leading' and 'Trailing' constraints to the superview. Add 'Top' constraint to the top box. Also, add 'Equal Heights' constraint to the superview and modify the multiplier to the required value.
For the last box, add 'Leading', 'Trailing' and 'Bottom' constraints to the superview. Add 'Top' constraint to the middle box.

Related

UIScrollview+autolayout seems not working in iOS8/Xcode 6 preview?

The following steps for UIScrollView+autolayout has been working for me, but not in the iOS8/Xcode 6 preview: (using storyboard, size class enabled):
add a scrollview to the root view.
pin zero spaces to all edges of super view.
add a UIView (contentView) to the above scrollview.
pin zero spaces to all edges of the scrollview
add some widgets to contentView and change the height of the contentView to 2000.
=> this contentView scrolls in iOS 7, but I cannot get the same steps working in iOS 8 preview.
Even it seems working in iOS 7, it is possible that I may not doing the right way? Any suggestions?
I'm surprised not to have seen more comment about this. Scroll view internal autolayout is largely broken in iOS 8 (as seeded up to the time of this writing).
EDIT This was fixed in seed 5, so this note should be ignored!
The rule is supposed to be (see https://developer.apple.com/library/prerelease/ios/technotes/tn2154/_index.html) that if the content of a scroll view (its subview or subviews) is pinned to all four bounds of the scroll view, it sets the content size.
In iOS 8, however, this fails - but in an odd way. It fails only if the constraints determining the height and width of the subviews are all absolute as opposed to intrinsic.
Thus, for example, consider the code at the bottom of that tech note, where a scroll view and a really big image view are created in code (here it is; I have corrected a small typo where an at-sign was dropped):
- (void)viewDidLoad {
UIScrollView *scrollView;
UIImageView *imageView;
NSDictionary *viewsDictionary;
// Create the scroll view and the image view.
scrollView = [[UIScrollView alloc] init];
imageView = [[UIImageView alloc] init];
// Add an image to the image view.
[imageView setImage:[UIImage imageNamed:#"MyReallyBigImage"]];
// Add the scroll view to our view.
[self.view addSubview:scrollView];
// Add the image view to the scroll view.
[scrollView addSubview:imageView];
// Set the translatesAutoresizingMaskIntoConstraints to NO so that the views
// autoresizing mask is not translated into auto layout constraints.
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
imageView.translatesAutoresizingMaskIntoConstraints = NO;
// Set the constraints for the scroll view and the image view.
viewsDictionary = NSDictionaryOfVariableBindings(scrollView, imageView);
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|[scrollView]|"
options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[scrollView]|"
options:0 metrics: 0 views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|[imageView]|"
options:0 metrics: 0 views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[imageView]|"
options:0 metrics: 0 views:viewsDictionary]];
}
That code works (assuming you have a really big image), because the image view is sized by intrinsic constraints. But now change the last two lines, like this:
[scrollView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|[imageView(1000)]|"
options:0 metrics: 0 views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[imageView(1000)]|"
options:0 metrics: 0 views:viewsDictionary]];
Now what you have is a scroll view that is scrollable on iOS 7 but is NOT scrollable on iOS 8. Further investigation shows that this is because the content size remains at (0,0); it does not respect the absolute width and height constraints of the content view.
Use following step for UIScrollView + AutoLayout
Add scroll view to the root view
Add contain view to above scroll view
Add Following constraint for scroll view
Trailing space to super view = 0
Leading Space to super view =0
Top space to super view = 0
Bottom Space to super view = 0
Add Following Constraint for contain view of scroll view
(in this case scroll view is super view)
Trailing space to super view = 0
Leading Space to super view =0
Top space to super view = 0
Bottom Space to super view = 0
Height of contain view (if you are using vertical scrolling) otherwise width of contain view (if you are using Horizontal scrolling).
Horizontal canter alignment (if you are using vertical scrolling) otherwise vertical canter alignment (if you are using Horizontal scrolling).

How do I use NSLayoutConstraint to make a subview take up remaining/total height of superview

I have a superview to wich I add two subviews (subview1 and subview2).
I want subview1 to have same width as superview and stretch with superview and also have a height of 30px and align to the top of superview. This I can get working with the following code:
NSDictionary *metrics = #{#"height":[NSNumber numberWithFloat:30.0f]};
NSDictionary *views = NSDictionaryOfVariableBindings(subview1);
NSArray *tabContainerConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[subview1(==height)]" options:NSLayoutFormatAlignAllTop metrics:metrics views:views];
[superview addConstraints:tabContainerConstraints];
metrics = nil;
tabContainerConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|[subview1]|" options:(NSLayoutFormatAlignAllLeading | NSLayoutFormatAlignAllTrailing) metrics:metrics views:views];
[superview addConstraints:tabContainerConstraints];
Then I want subview2 to also have same width as superview and stretch with superview and also I want subview2 to align its top to subview1's bottom and then I want subview2 to fill all of the remaining height of superview (align bottom to bottom) and stretch in height with superview. I try to do this with this code:
NSDictionary *metrics = nil;
NSDictionary *views = NSDictionaryOfVariableBindings(subview2);
NSArray *tabContainerConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|[subview2]|" options:(NSLayoutFormatAlignAllLeading | NSLayoutFormatAlignAllTrailing) metrics:metrics views:views];
[superview addConstraints:tabContainerConstraints];
views = NSDictionaryOfVariableBindings(subview1, subview2);
tabContainerConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[subview1][subview2]-0-|" options:0 metrics:metrics views:views];
[superview addConstraints:tabContainerConstraints];
But strange things happens... subview is always aligned to with its top to subview1's bottom and also has the full width of superview so this is good. But height is strange... When first drawn/displayed subview2 has a very limited height, somewhere between 20-30 pixel it seems, but I can force a redraw by switching to another tab/view and back and then it is drawn in full/correct height. My subview2 is a NSTextView, and when I type in text strange things happen, my subview2 suddenly does not take up all height and is no longer aligned with the bottom of superview.
I hope my explanation is ok, if not please ask any question. Any ideas on how to fix this? I thought the |-0-[view]-0-| would do the trick?
Thank you
Søren
It's not clear from your question whether you're running all of that code or the first snippet is for one view only and the second snippet is for two views.
You should be combining the two VFL strings into one, and you don't need some of the layout options that you are supplying. If you only have one view in the VFL string, the layout options are meaningless.
I'd suggest the following code for creating your constraints:
NSDictionary *metrics = #{#"height":[NSNumber numberWithFloat:30.0f]};
NSDictionary *views = NSDictionaryOfVariableBindings(subview1,subview2);
// Horizontal layout for subview 1
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[subview1]|" options:0 metrics:metrics views:views]];
// Vertical layout - the options here mean that subview2 will be the same width as subview1
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[subview1(==height)][subview2]|" options:NSLayoutFormatAlignAllLeft | NSLayoutFormatAlignAllRight metrics:metrics views:views]];
You may be having some issues with the intrinsic size of the text view, if it behaves differently when it has content inside it or not. Or, the superview itself may be changing its size - what are the constraints on the superview? You will need to set some borders / background colours or use an introspection tool to determine which frames are going funny.

How do I resize a textview and scrollview content size based on dynamic content

I have a scrollview that contains an image and a single textview. The content of the textview is dynamic it could be 200 in height or it could be 1500 in height. I am using storyboard so how do I setup both the textview and the scrollview content size to allow for dynamic content in the textview. I can set them both to arbitrarily high number but then I end up with a bunch empty space in the scrollview. I want both the image and the text to scrolling on and off the screen as required.
You would use constraints. Here is an example of how I did it with a scrollview. With a textview it is scrollable, and doesn't require you to set a content size, you simply put the content in it. If you want to control the viewable area of the uitextview, you will need to adjust it to what you see fit based on the amount of text you have, font and spacing you use.
NSDictionary *viewsDic = #{
#"image":image,
#"text": text,
#"lable":label,
#"someview":someview,
#"anotherview":anotherview};
[someview addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[image(320)]|"
options:0
metrics:0
views:viewsDic]];
[someview addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[image]|"
options:0
metrics:0
views:viewsDic]];
[someview addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-14-[label]"
#"[text(<=200)]"
options:0
metrics:0
views:viewsDic]];
[someview addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-22-[txtDollarAmountFront(==37)]"
options:0
metrics:0
views:viewsDic]];
[someview addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-19-[label]"
options:0
metrics:0

NSTableView's frame inside a NSClipView/NSScrollView using auto layout

I'm trying to create a NSTableView inside a NSScrollView (the standard configuration, that is) in code, using auto layout. I can't figure out how to make this work.
Here's my loadView:
- (void)loadView
{
NSView *view = [[NSView alloc] init];
NSScrollView *tableScroll = [[NSScrollView alloc] init];
NSTableView *fileTable = [[NSTableView alloc] init];
[tableScroll setDocumentView:fileTable];
[tableScroll setHasVerticalScroller:YES];
[tableScroll setHasHorizontalScroller:NO];
fileTable.delegate = self;
fileTable.dataSource = self;
[fileTable setHeaderView:nil];
[fileTable setAllowsColumnReordering:NO];
NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:#"column1"];
[fileTable addTableColumn:column];
[tableScroll setTranslatesAutoresizingMaskIntoConstraints:NO];
[fileTable setTranslatesAutoresizingMaskIntoConstraints:NO];
[view addSubview:tableScroll];
NSDictionary *topViews = NSDictionaryOfVariableBindings(tableScroll);
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[tableScroll]|" options:0 metrics:nil views:topViews]];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[tableScroll]|" options:0 metrics:nil views:topViews]];
self.fileTable = fileTable;
self.view = view;
}
What happens is that my table view's frame will be always equal to the bounds of the NSClipView. The view is inside a window and gets resized with it, and when I do that it'll resize the scrollview, the clip view and the table, but I can never scroll anywhere.
Looking at constraints I get, the NSScrollView gets constraints that set the clip view to fill it, the clip view has no constraints at all and the table view has a bunch of constraints related to the NSTableRowViews inside it.
If I add a constraint like |[fileTable(>=500)] to the clip view I'll get 500 pixels of NSTableView, but obviously I don't want to do that.
Even though this was answered by the poster in the comments above, I thought I’d put the answer here (having run into the same issue). If you are adopting auto layout, you would typically uncheck “Translates Mask Into Constraints” in the xib. However, for classes like NSScrollView and NSTableView, you should generally let them manage their own internal views by setting their translatesAutoresizingMaskIntoConstraints property to YES. It is still ok to set constraints that are external to these views, i.e. to resize in relation to their superview.
If you set translatesAutoresizingMaskIntoConstraints to NO, then you will need to supply constraints for all of the internal views, which unless you specifically need custom behavior (almost never), you will not want to do. This was the specific problem above.
An obvious side effect of not setting this correctly is that a table (for example) will not properly scroll beyond what is visible in the view.

In OSX 10.8 how do I constrain a subview to be the same size as its parent view

I have the default NSWindow created in a new application which has a single NSView. I then create a new NSViewController which has it's own XIB and a view. In the app delegate I do the obvious
self.mainViewController = [[MainViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
[self.window.contentView addSubview:self.mainViewController.view];
self.mainViewController.view.frame = ((NSView*)self.window.contentView).bounds;
OK, how do I set a constraint in the new way to have my subview keep its size identical to the Window, i.e. it's superview. It doesn't seem to work automatically. Autoresizessubviews is ON for both views.
Basically, you need to constrain four things:
The leading space of your subview to its superview to be zero
The top space of your subview to its superview to be zero
The width of your subview to be equal to its superview's width
The height of your subview to be equal to its superview's width
If the visual constraint isn't working out for you, you can build these four constraints individually in code. Use the method +constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:
constant: to specify exact relationships between different views' attributes. For example, constraint #1 above might be expressed by:
[NSLayoutConstraint constraintWithItem:mySubview
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:mySuperview
attribute:NSLayoutAttributeLeading
multiplier:1.0f
constant:0.0f]
and #3 might be:
[NSLayoutConstraint constraintWithItem:mySubview
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:mySuperview
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:0.0f]
Once you've built up those four constraints, you can add them to your superview as needed.
Note that there are multiple ways to achieve the same effect as above:
You might constrain the trailing space and bottom space instead of the width and height
You might constrain the center X and center Y instead of the leading and top spaces
You can also probably come up with the same constraints in a visual representation, as in Peter Hosey's answer. For example, an equal-width constraint might look like #"[mySubview(==mySuperview)]" with the appropriate views dictionary.
Keep in mind that the Auto Layout Guide is a wealth of information about constraints, including how to debug them when things go wrong.
In the nib editor, drag the subview's size until it is the same size as its superview. Xcode will create an appropriate width constraint automatically.
In code, I would try |-0-[mySubview]-0-| (adapted from the example in the constraint syntax documentation).
Just like Peter wrote, you should use the visual format language.
When doing this, however, order is important: when you create a constraint, all views it references have to be part of the same view hierarchy.
So given your example, the code would have to become:
self.mainViewController = [[MainViewController alloc] initWithNibName:#"MainViewContoller" bundle:nil];
NSView *containerView = self.window.contentView;
NSView *contentView = self.mainViewController.view;
[contentView setTranslatesAutoresizingMaskIntoConstraints: NO];
[containerView addSubview:contentView];
NSDictionary *viewBindings = NSDictionaryOfVariableBindings(contentView);
[containerView addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[contentView]|" options:0 metrics:nil views:viewBindings]];
[containerView addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[contentView]|" options:0 metrics:nil views:viewBindings]];
You can override setContentView:, contentView:, and contentRectForFrameRect: so they will deal with window.frame - sized view.
If you're ok with using a 3rd party library, you can accomplish this with ReactiveCocoaLayout in one simple line:
RAC(view,rcl_frame) = parentView.rcl_frameSignal;
I had the same problem and I ended up with this solution which is working with SDK 10.10. Just set the autoresizingMask of the new view to be the same as the parent window. Only one row of code and it works like a charm...
self.masterViewController = [[MasterViewController alloc] initWithNibName:#"MasterViewController" bundle:nil];
[self.window.contentView addSubview:self.masterViewController.view];
self.masterViewController.view.frame = ((NSView *)self.window.contentView).bounds;
self.masterViewController.view.autoresizingMask = ((NSView *)self.window.contentView).autoresizingMask;

Resources