Auto layout constraint for different screen size - ios

I have the following constraint :
self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"V:|-(>=%f,<=%f)-[topImageView(>=120,<=170)]-|",navHeight,navHeight+40] options:0 metrics:0 views:viewsDictionary]];
When I run this on iPad, it seems fine. The view is navHeight+40 from top. but when I run it on iPhone, it is still navHeight+40 from top and not navHeight. Same goes for height.

Related

AutoLayout: Vertical to horizontal with no explicit sizes?

I am interesting in accomplishing something of this kind, using preferably only AutoLayout and no code:
Here are the constraints, that I need to maintain:
No explicit view sizes, as both iPhone and iPad should be supported.
View 1 maintains its square aspect ratio.
View 2 takes all the remaining space (vertical or horizontal) and resizes its subviews.
On rotation, change from vertical alignment to horizontal.
I would appreciate any suggestions on how something like this can be accomplished. AutoLayout seems a bit confusing for this kind of task.
Thanks a lot!
P.S. - As a more complicated follow-up, do you think something like this is achievable? I am a bit skeptical.
All of this is definitely possible with Auto Layout. It will just require some code!
To address each bullet point:
This is what AutoLayout is all about. Depending on your implementation, you should actually have a difficult time NOT making it work on both the iPad and iPhone.
No problem
[view1 addConstraint:[NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:view1 attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0]];
Sure can
UIView *view1;
UIView *view2;
UIView *superview;
NSDictionary *metrics = #{#"border":#(VIEW1_BORDER)};
NSDictionary *views = NSDictionaryOfVariableBindings(view1,view2);
UIDeviceOrientation orientation = [[UIDevice currentDevice]orientation];
if (UIDeviceOrientationIsPortrait(orientation)) {
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[view1]-(border)-[view2]|" options:0 metrics:metrics views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view2]|" options:0 metrics:nil views:views]];
} else {
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[view1]-(border)-[view2]|" options:0 metrics:metrics views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[view2]|" options:0 metrics:nil views:views]];
}
Implement the UIViewController willRotateToInterfaceOrientation:duration: method and use the same code from above. Just be sure to remove any previously added constraints before adding the new ones to avoid conflicting constraints. UPDATE: Since willRotateToInterfaceOrientation:duration: is deprecated in iOS 8, you could observe the default NSNotificationCenter for
UIApplicationWillChangeStatusBarOrientationNotification and
UIApplicationDidChangeStatusBarOrientationNotification
notifications to handle orientation changes.
Using three views would be nearly exact same code as above.

AutoLayout: Shifting from Portrait to Landscape doesn't update the UIScrollView Width

In my application I'm using ScrollView to show full size images horizontally and its running perfect in Portrait mode but when I shift it to Landscape mode then the width of scrollview doesn't update.
Lets say I've 4 images and width of each image is 568 in Landscape mode so the total width of UIScrollView should be 2272 and actually it is. but it only show content of width 1136 i.e. 2.5 out of 4. And If I go to previous view and reload this view in Landscape then scrollview shows all the contents within its width (2272).
Here is my Views hierarchy:
I'm adding constraints as follow:
[mainScrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"V:|[imgView(%f)]|", self.view.frame.size.height]
options:kNilOptions
metrics:nil
views:views]];
[mainScrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"H:|[imgView(%d)]|", width_size]
options:kNilOptions
metrics:nil
views:views]];
[mainScrollView updateConstraints];
where width_size = 2272
You can find my Code there :
https://drive.google.com/file/d/0B1noH4KPfAePTVVNb3lhZXlmZzA/edit?usp=sharing
I'm new to AutoLayout and stuck and don't know why this is happening, any help will be greatly appreciated.

Adding a dynamically sized view (height) to a UIScrollView with Auto Layout

Here is my structure of views for this detail view (blogging application, I want to view the entire post which has dynamic height inside of a scrollview):
UIView
-UIScrollView
-SinglePostView (custom view)
-Title
-Subtitle
-Content
Normally to add a 'single post view' to a UIView I simply instantiate it, feed it my Post object and then add a single constraint that pins the width of the singlePostView to the superview, at which point things get laid out nicely. However when I try to add it to a scroll view, it doesn't show up nor does it scroll.
UIScrollView *scrollView = [[UIScrollView alloc] init];
[self.view addSubview:scrollView];
NOBSinglePostView *singlePost = [[NOBSinglePostView alloc] initWithPost:self.post];
[scrollView addSubview:singlePost];
singlePost.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(scrollView,singlePost);
[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:|[singlePost]|" options:0 metrics: 0 views:viewsDictionary]];
In the structure you are presenting, using AutoLayout, the contentSize of your UIScrollView is driven by the intrinsicContentSize of its subviews, here your SinglePostView.
The problem is, SinglePostView being a subclass of UIView, its intrinsicContentSize is always CGSizeZero when considered by itself. What you need to do is make the intrinsicContentSize of your SinglePostView depend on the intrinsicContentSize of its subviews.
Then, because the subviews of your SinglePostView are UILabels, and because a UILabel's intrinsicContentSize is the smallest size it needs to display its content, your SinglePostView's intrinsicContentSize will be equal to the sum of its subviews intrinsicContentSizes, that is the total size needed to display the content of all three of your labels.
Here is how to do it.
Step 1: Removing all automatically set constraints
First, as you partially did, you need to remove all constraints automatically set by the system.
Assuming you don't have any constraints set in your storyboard or XIB (or you don't even have one of these), just do:
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
singlePost.translatesAutoresizingMaskIntoConstraints = NO;
titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO;
contentLabel.translatesAutoresizingMaskIntoConstraints = NO;
Now you have a clear slate and you can start setting your own constraints.
Step 2: Constraining the scrollView
First, let's create, as you did, the views references dictionary for AutoLayout to use:
NSDictionary *views = NSDictionaryOfVariableBindings(scrollView, singlePost, titleLabel, subtitleLabel, contentLabel);
Then, also as you already did, let's constrain the scroll view to be the size of its superview:
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[scrollView]-0-|" options:0 metrics:0 views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[scrollView]-0-|" options:0 metrics:0 views:views]];
Step 3: Constraining the SinglePostView to push the scrollView's contentSize
For this step to be clear, you have to understand that every constraints set between a UIScrollView and its subviews will actually change the contentSize of the UIScrollView, not its actual bounds. For Example, if you constrain a UIImageView to the borders of its parent UIScrollView and then put an image twice the size of the UIScrollView inside the UIImageView, your image won't get shrunk, its the UIImageView that will take the size of its image and become scrollable inside the UIScrollView.
So here is what you have to set here:
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[singlePost]-0-|" options:0 metrics:0 views:views]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[singlePost]-0-|" options:0 metrics:0 views:views]];
[scrollView addConstraint:[NSLayoutConstraint constraintWithItem:singlePost
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:scrollView
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:0.0f]];
First two constraints are pretty obvious. The third one, however, is here because, for your UILabels to display their content properly and still be readable, you will probably want them to be multilined and the scrolling to be vertical, not horizontal. That's why you set your SinglePostView's width to be the same as your scrollView's. This way, you prevent your scrollView's contentSize.width to be anything more than its bounds.width.
Step 4: Constraining your UILabels to "push" the bounds of your SinglePostView
Fourth and final step, you now need to set constraints on your SinglePostView's subviews, so that it gets an intrinsicContentSize from them.
Here is how you do it (simplest implementation, no margins, one label after the other vertically):
[singlePost addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[titleLabel]-0-|" options:0 metrics:0 views:views]];
[singlePost addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[subtitleLabel]-0-|" options:0 metrics:0 views:views]];
[singlePost addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[contentLabel]-0-|" options:0 metrics:0 views:views]];
[singlePost addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[titleLabel]-0-[subtitleLabel]-[contentLabel]-0-|" options:0 metrics:0 views:views]];
And with that you should be done.
One last advice, you should definitely look into UIStoryboard to do these kinds of things. It's a lot simpler and more visual.
Hope this helps,
P.S.: If you want, I can take some time and push a project using both UIStoryboard and the Visual Format Language on Github. Just tell me if you would need one.
Good luck.
in auto layout
frame of scrollview is decided by constraints between scrollview and superview of scrollview.
contentSize of scrollview is decided by constraints between scrollview and subview of scrollview.
you should set the size of singlePostView. ScrollView calculate contentSize from it. (you need to add size constraints explicitly)
CGFloat heightOfSinglePostView = calculate..
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[singlePost(heightOfSinglePostView)]|" options:0 metrics: 0 views:viewsDictionary]];

Creating these constraints in code, advice required

So I am having extreme difficulties in understanding how I can create these same constraints that the interface builder so nicely has created for me.
I have been reading the document on apple developer site and trying to follow it, but I can't seem to get these work. I am creating a custom view controller that adds a subview and creates those same constraints for that new view when it's pushed in.(The bottom constraint is where the new view is pushed, the top view is ALWAYS the same) I have written this following code but it doesn't seem to work properly(for example when I simulate in-call status bar, the views don't act like on the initial view where IB has created the constraints)
My code:
QVViewController * __weak vc1 = (QVViewController*)self.rootViewController2.parentViewController;
UIView *viewToBePushed = tempV.view;
UIView *topContainerView = self.rootViewController1.view;
id bottomLayoutGuide = tempV.bottomLayoutGuide;
[vc1.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[viewToBePushed]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(viewToBePushed)]];
[vc1.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[topContainerView]-0-[viewToBePushed]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(viewToBePushed,topContainerView)]];
[vc1.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-138-[viewToBePushed]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(viewToBePushed)]];
[vc1.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[viewToBePushed]|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(viewToBePushed)]];
[vc1.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[viewToBePushed]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(viewToBePushed)]];
[vc1.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[bottomLayoutGuide]-0-[viewToBePushed]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(viewToBePushed, bottomLayoutGuide)]];
Basically
viewToBePushed represents the bottom container in the picture
topContainerView represents the top container in the picture
vc1 is the view where those two container are
I hope I explained the situation clearly enough, if not, ask and I can try to elaborate. I would be really grateful for help as these constraint things are stealing my good night sleep and I very much would want to write easily maintainable code.
So can someone show me how to correctly create those constraints in code.
Given your setup, I think the bottom container view should have whatever constraints you need, but they don't ever need to be changed. When you switch to a new controller embedded in that container view, you can just set constraints to all sides of that container (after viewThatWasPushed is added as a subview)
[self.bottomContainerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[viewThatWasPushed]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewThatWasPushed)]];
[self.bottomContainerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[viewThatWasPushed]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewThatWasPushed)]];
self.bottomContainerView is an IBOutlet to theta bottom view.

How to resolve ambiguous layout?

I want an iPad layout that that has two panels side by side, to fill the width of the screen and both are as tall as the screen. My attempts have led to as follows
self.view addConstraints:
#"|[_sidePanel(300)]-1.0-[_mainPanel]|"
#"V:|[_sidePanel]|"
#"V:|[_mainPanel]|"
Inside __sidePanel_ I'm trying to create more constraints on child views.
Note the _sidePanel view is a UIScrollView.
I want to stack 2 views on top of one another in the side panel.
So I add the following constraints to__sidePanel_.
_sidePanelView addConstraints:
#"|[_top(300)]|"
#"|[_bottom(300)]|"
#"V:|[_top]-5.0-[_bottom]|"
It seems I need to specify the width for these two views in order to avoid ambiguity.
But I want the bottom view to fill the remaining space of __sidePanel_.
If I just pin __bottom_ to the bottom of __top_ (which gets a defined height at some point based on its contents) and to the bottom of its parent __sidePanel_, the __sidePanel_ and __bottom_ are both ambiguous; which makes sense i guess since the constraints are awfully similar (and which doesn't get avoided by adding the constraint for __bottom_ to the __sidePanel_ view as opposed to the topmost self.view).
If I hardcode a height for __bottom_, i resolve ambiguity but I don't want a defined height; i want it to fill remaining space in __sidePanel_.
Any suggestions on what I could try to resolve ambiguity but still achieve what I'm after?
You need to specify a height for either top or bottom -- it sounds like top gets a defined height at some point, but you need set a defined height for it initially, which you can change later.
Also, there's no need to specify the widths (300) for either top or bottom, since you've pinned them to the sides of sidePanel, which itself has a defined width. so these constraints worked fine with no ambiguity:
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[_sidePanel(300)]-1.0-[_mainPanel]|" options:0 metrics:nil views:viewsDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_sidePanel]|" options:0 metrics:nil views:viewsDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_mainPanel]|" options:0 metrics:nil views:viewsDict]];
[_sidePanelView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[_top]|" options:0 metrics:nil views:viewsDict]];
[_sidePanelView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[_bottom]|" options:0 metrics:nil views:viewsDict]];
[_sidePanelView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_top]-5.0-[_bottom]|" options:0 metrics:nil views:viewsDict]];
self.topHeightCon = [NSLayoutConstraint constraintWithItem:self.top attribute:NSLayoutAttributeHeight relatedBy:0 toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:300];
[self.top addConstraint:self.topHeightCon];
Later, when you calculate the actual height for top, you can use self.topHeightCon.constant = (some value) to adjust its height.
In my case it came down to the fact that the view I was trying to have subviews constrain to its bounds was a UIScrollView, which wasn't happening. I since changed it to a UIView and voila my constraints work. And there you have it.

Resources