iOS programmatically fix the position of a subview within UIScrollView with Autolayout - ios

I have a UIView that holds a UIScrollView. The UIScrollView contains an MKMapView subview and a placeholder subview right below it. I would like to pin the MKMapView to the top of the screen and allow the placeholder subview to slide over it and cover it up.
Apple says it's now possible to accomplish this with Autolayout but it doesn't seem to be working for me. The code below displays the UIScrollView and it's subviews properly, but the map still scrolls along with everything else. Am I missing something really obvious?
https://developer.apple.com/LIBRARY/ios/technotes/tn2154/_index.html
Note that you can make a subview of the scroll view appear to float (not scroll) over the other scrolling content by creating constraints between the view and a view outside the scroll view’s subtree, such as the scroll view’s superview.
UIView .m file:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Create scroll view and add to view
UIScrollView *scrollView = [[UIScrollView alloc] init];
[scrollView setBackgroundColor: [UIColor clearColor]];
[scrollView setShowsVerticalScrollIndicator:NO];
[scrollView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:scrollView];
// Create map view and add to scroll view
mapView = [[MKMapView alloc] init];
mapView.showsPointsOfInterest = NO;
mapView.translatesAutoresizingMaskIntoConstraints = NO;
[scrollView addSubview:mapView];
// Create a placeholder image to scroll over map view
UIImageView *randomPlaceholderStuff = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"stuff.png"]];
[dividingLine setTranslatesAutoresizingMaskIntoConstraints:NO];
[scrollView addSubview:dividingLine];
// Layouts
NSDictionary *viewArranging = NSDictionaryOfVariableBindings(scrollView, tourMap, randomPlaceholderStuff);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|"
options:0
metrics:0
views:viewArranging]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|"
options:0
metrics:0
views:viewArranging]];
UIView *referenceSuperView = scrollView.superview;
[referenceSuperView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[mapView(320)]|"
options:0
metrics:0
views:viewArranging]];
[referenceSuperView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[mapView(320)]"
options:0
metrics:0
views:viewArranging]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-334-[randomPlaceholderStuff]|"
options:0
metrics:0
views:viewArranging]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[randomPlaceholderStuff(320)]|"
options:0
metrics:0
views:viewArranging]];
}
return self;
}
#end
Edit:
jrturton's answer was spot on. Here's the code that ended up working:
[self addConstraint:[NSLayoutConstraint constraintWithItem:mapView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:scrollView.superview attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:mapView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:scrollView.superview attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:mapView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:320.0]];

You can't do what you're trying with visual format language. The | character will always be interpreted as the view's direct superview, so you're not creating the constraints you think you are.
You need to use the longer method (constraintWithItem...) to create individual constraints, where item1 is your floating view, and item2 is the scroll view's superview.

You can add floating sub view over UIScrollView by adding/moving subview to super view of scrollview like:
Place/set your button over scroll view (not inside scroll view) as shown here in this snapshot. And also set button constraints (position) with respect to super view of your scrollview.
Here is ref. snapshot of hierarchy of position of each view over each-other.

Related

UIScrollView with Auto Layout Constraints: Auto Content Size Calculation

I'm having troubles with UIScrollView using auto layout constraints. I have the following view hierarchy, with constraints set through IB:
- ScrollView (leading, trailing, bottom and top spaces to Superview)
-- ContainerView (leading, trailing, bottom and top spaces to ScrollView)
--- Button 1 (full width, **top space to ContainerView**)
--- Button 2 (full width, below Button 1)
--- Button n (full width, below Button n-1, **bottom space to ContainerView**)
I want a simple scrollabel list of buttons. Here is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view setBackgroundColor:[UIColor redColor]];
[self.contentView setBackgroundColor:[UIColor yellowColor]];
UIView *lastView= self.contentView; // use for top constraint
NSInteger topBottomMargin= 10, leftRightMargin= 16;
for (int i=0; i<10; i++) {
UIButton *button= [UIButton buttonWithType:UIButtonTypeSystem];
button.translatesAutoresizingMaskIntoConstraints= NO;
[button setTitle:[NSString stringWithFormat:#"Button %d", i] forState:UIControlStateNormal];
[self.contentView addSubview:button];
// add constraints
// top
[self.contentView addConstraint:[NSLayoutConstraint
constraintWithItem:lastView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:button
attribute:NSLayoutAttributeTop
multiplier:1.0 constant:-topBottomMargin]];
// left
[self.contentView addConstraint:[NSLayoutConstraint
constraintWithItem:self.contentView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:button
attribute:NSLayoutAttributeLeading
multiplier:1.0 constant:-leftRightMargin]];
// right
[self.contentView addConstraint:[NSLayoutConstraint
constraintWithItem:self.contentView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:button
attribute:NSLayoutAttributeTrailing
multiplier:1.0 constant:leftRightMargin]];
lastView= button;
}
// bottom
[self.contentView addConstraint:[NSLayoutConstraint
constraintWithItem:self.contentView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:lastView
attribute:NSLayoutAttributeBottom
multiplier:1.0 constant:topBottomMargin]];
}
It seems the height of contentView is 0! But there are constraints both for top and bottom of it. It should be like this:
But with my code it's like this. Any Help would be great.
You can add constraint to container view to scroll view as Equal height & equal width. Also when you add constraint to buttons don't forget add bottom constraints to buttons as it will decide the end of scroll view(content size).
Since you are using auto-layout constraints on the contentView, it's height (frame) will be zero in the viewDidLoad method. You should move your code into the viewDidLayoutSubviews method and try to add your buttons there.
You should get the height of the contentView there. Please let me know if that works. Hope this helps.
See this question for reference: iOS AutoLayout - get frame size width
I don't think we can't add auto layout directly to a ContainerView inside ScrollView with Intrinsic Size as Default, so I add ContainerView as subview programmatically:
- (void)viewDidLoad{
[super viewDidLoad];
self.contentView = [[UIView alloc] initWithFrame:CGRectZero];
[self.scrollView addSubview:self.contentView];
//Then add your button here as normal.
//...
}
And Gurtej Singh is right, we have to update the frame in the viewDidLayoutSubviews:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
//Don't for get to update your self.scrollView.contentSize if you want to able to scroll
//...
//Update your contentView frame based on scrollview frame and self.scrollView.contentSize.
self.contentView.frame = self.scrollView.bounds or ....;
}
I just want to help, it might not a good solution, but it work for me.
I found the solution i was looking for here:
[super viewDidLoad];
UIScrollView* sv = [UIScrollView new];
sv.backgroundColor = [UIColor whiteColor];
sv.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:sv];
[self.view addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[sv]|"
options:0 metrics:nil
views:#{#"sv":sv}]];
[self.view addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[sv]|"
options:0 metrics:nil
views:#{#"sv":sv}]];
UILabel* previousLab = nil;
for (int i=0; i<30; i++) {
UILabel* lab = [UILabel new];
lab.translatesAutoresizingMaskIntoConstraints = NO;
lab.text = [NSString stringWithFormat:#"This is label %i", i+1];
[sv addSubview:lab];
[sv addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(10)-[lab]"
options:0 metrics:nil
views:#{#"lab":lab}]];
if (!previousLab) { // first one, pin to top
[sv addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(10)-[lab]"
options:0 metrics:nil
views:#{#"lab":lab}]];
} else { // all others, pin to previous
[sv addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"V:[prev]-(10)-[lab]"
options:0 metrics:nil
views:#{#"lab":lab, #"prev":previousLab}]];
}
previousLab = lab;
}
// last one, pin to bottom and right, this dictates content size height
[sv addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:[lab]-(10)-|"
options:0 metrics:nil
views:#{#"lab":previousLab}]];
[sv addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:[lab]-(10)-|"
options:0 metrics:nil
views:#{#"lab":previousLab}]];
// look, Ma, no contentSize!

iOS Auto Layout When Loading Another Nib

I have a detail view in a story board. I want to load another Nib in that view when a certain condition is met. But when I do, the auto layout its all screwed you.
I am trying to display the UIView above the UIWebView like an overlay view. I want the UIView to have the same ratio of the device with a maximum height of 400 between the top and bottom layout guide.
Here is the code I used to load the Nib:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController.navigationBar setTintColor:[UIColor whiteColor]];
// Do any additional setup after loading the view, typically from a nib.
UINib *s = [UINib nibWithNibName:#"Square1" bundle:nil];
NSArray *array = [s instantiateWithOwner:self options:nil];
StopView *stopView = (StopView *)[array objectAtIndex:0];
[stopView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:stopView];
id topGuide = self.topLayoutGuide;
id bottomGuide = self.bottomLayoutGuide;
UIWebView *webView = self.detailWebView;
NSDictionary *views = NSDictionaryOfVariableBindings(stopView, topGuide, bottomGuide, webView);
// this is here to stop the auto layout from reporting that the guides has
// ambiguous layout
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[topGuide]|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[bottomGuide]|" options:0 metrics:nil views:views]];
// center the stop view in the super view, both lines below are needed
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|-(>=12)-[stopView(<=400)]-(>=12)-|"
options: 0
metrics:nil
views:views]];
// set the height to a ratio of the width
NSLayoutConstraint *con2 = [NSLayoutConstraint constraintWithItem:stopView
attribute:NSLayoutAttributeWidth
relatedBy:0 toItem:stopView
attribute:NSLayoutAttributeHeight
multiplier:0.66667f constant:0];
[self.view addConstraint:con2];
// center the Stop View X,Y with the super view
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:stopView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0f constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:stopView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1.0f constant:0]];
NSLog(#"Calling configureView from viewDidLoad");
[self configureView];
}
Here are some screen shots:
As you can see in the third screen shot, my background is not showing. And you can see the T from the UILabel that placed at the top in design mode.
What am I doing incorrectly?
For a vertical constraint, you need to put a "V:" in front of the string. To get a view to be 400 max, but be as big as it can given the top and bottom spacing constraints for a smaller screen, you need to use the priority of the constraint,
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(>=12)-[stopView(==400#900)]-(>=12)-|"
options: 0
metrics:nil
views:views]];
The system will try to make the height of stopView as close to 400 as possible while maintaining a spacing of at least 12 to the top and bottom.

How to resize custom UITableView separators on landscape and prevent from disappearing

I've decided to programmatically create my own UITableView separator lines because I need fine control over displaying a separator above and/or below each individual UITableViewCell. My tableView has static cells, so I do not create the separators in cellForRowAtIndexPath. Instead, I have propertys for each cell and in viewDidLoad, I add a top and/or bottom separator as needed. It's working, until I rotate to landscape and then the separator line does not stretch to fill the screen - it of course remains the same width it was when created. I'm not seeing how I can automatically adjust them to fit the width of the screen.
I tried adding Auto Layout constraints (leading, trailing, top/bottom), but for some reason it's not working - the width does not change, but there are no error messages logged to indicate anything is wrong with the constraints. The separator lines also sometimes disappear upon scroll or rotate, and if I comment out the auto layout constraints then they do not disappear.
So how can I make my custom cell separators always stretch to fill the device width upon rotation, and how do I prevent them from disappearing?
If it would be easier/better to create my custom cell separators in a different way, I am willing to do that. I just don't know how this can be done aside from my approach when the cells are static. I considered creating the views in the Storyboard, and setting up the constraints visually, but would that not be the equivalent of what I'm doing programmatically? If they were dynamic cells I would do it in cellForRowAtIndexPath.
//In viewDidLoad:
[self addTopSeparatorForCell:self.myCell];
//Helper method
- (void)addTopSeparatorForCell:(UITableViewCell *)cell {
UIView *topSeparator = [[UIView alloc] initWithFrame:CGRectMake(15, 1, cell.contentView.frame.size.width, 0.5)];
//add CALayer to preserve line separator visibility when row is highlighted
CALayer *backgroundColorLayer = [CALayer layer];
backgroundColorLayer.frame = topSeparator.bounds;
backgroundColorLayer.backgroundColor = [UIColor colorWithWhite:204/255.0f alpha:1].CGColor;
[topSeparator.layer addSublayer:backgroundColorLayer];
[cell.contentView addSubview:topSeparator];
//add auto layout constraints
topSeparator.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *cn = nil;
cn = [NSLayoutConstraint constraintWithItem:topSeparator
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:cell.contentView
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:15];
[cell.contentView addConstraint:cn];
cn = [NSLayoutConstraint constraintWithItem:topSeparator
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:cell.contentView
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:0];
[cell.contentView addConstraint:cn];
cn = [NSLayoutConstraint constraintWithItem:topSeparator
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:cell.contentView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:1];
[cell.contentView addConstraint:cn];
}
EDIT: Thanks to # user1966109, we've been able to solve the issue with the lines not extending to fill the width, and now they are preserved when highlighting a cell. But one issue still remains that I haven't been able to solve, since I'm not sure why it's occurring. The separator lines disappear after scrolling down the scrolling back up. It's related to the auto layout constraints though because a previous solution which had other issues did not exhibit this problem. Here's the current solution that causes the lines to disappear. I'd appreciate it if someone knows how to prevent this problem (and preserve the other issues already resolved).
[self.cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(15#750)-[myView]-(-47#750)-|" options:0 metrics:0 views:viewsDictionary]];
[self.cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[myView(2)]-(-2#750)-|" options:0 metrics:0 views:viewsDictionary]];
You should not mix initWithFrame and Auto Layout. You can have a good result with a few lines using Visual Format Language for Auto layout:
//#interface TableViewController ()
#property (weak, nonatomic) IBOutlet UITableViewCell *cell;
//#implementation TableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *myView = [[UIView alloc] init];
myView.backgroundColor = [UIColor redColor];
[self.cell.contentView addSubview:myView];
myView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(myView);
[self.cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[myView]|" options:0 metrics:0 views:viewsDictionary]];
[self.cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[myView(2)]" options:0 metrics:0 views:viewsDictionary]];
}
This handles rotation perfectly.
Edit!
Set the following constraints if using a accessory view:
//Set a negative value to the trailing space in order to display myView under the accessory view
//Those constraints work for both self.cell and self.cell.contentView (kind of odd)
[self.cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(15#750)-[myView]-(-47#750)-|" options:0 metrics:0 views:viewsDictionary]];
[self.cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[myView(2)]-(-2#750)-|" options:0 metrics:0 views:viewsDictionary]];
With the initial help of user1966109, I have figured out constraints that address all of the problems and are working well:
[cell addConstraint:[NSLayoutConstraint constraintWithItem:imageView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:cell
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:indent]];
[cell addConstraint:[NSLayoutConstraint constraintWithItem:imageView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:cell
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:0.0]];
[cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[imageView(0.5)]" options:0 metrics:0 views:viewsDictionary]];

Programmatically creating "spacing to nearest neighbor" constraint

Part of the screen I'm building includes a section with n views. I'm generating these views on the fly in code--they're simple UIView subclasses.
I'm using AutoLayout constraints for this screen, and I'd like each view to automatically position itself 15px or so below the view above it.
In Xcode it's possible to create a spacing to nearest neighbor constraint, which seems to do exactly what I want.
However, I can't seem to find any examples that show how to create this in code.
Is it possible to create a "spacing to nearest neighbor" constraint programmatically?
I was actually doing some personal exercises of auto-layout and animations when I stumbled over your question and decided to extend my a small demo, which you can download here.
If I have understood you correctly below piece of code can be used for inspiration. With a small effort it could be extended with removal of views too and also with dynamic height of the views.
Please note my code includes a solution with and without animation - the latter is of course more simple.
#import "Demo2ViewController.h"
#interface Demo2ViewController ()
{
NSMutableArray *_viewList;
NSDictionary *_metrics;
}
#end
#implementation Demo2ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[[self view] setBackgroundColor:[UIColor colorWithRed:.95 green:.95 blue:.95 alpha:1.0]];
_metrics = #{#"height": #30, // height of the views being added
#"space": #15}; // space between two views
// the first view
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectNull];
_viewList = [[NSMutableArray alloc] initWithObjects:textView, nil];
textView.text = [NSString stringWithFormat:#"view: %lu", (unsigned long)[_viewList count]];
// a button to add more views
UIButton *buttonAddView = [[UIButton alloc] initWithFrame:CGRectNull];
[buttonAddView setTitle:#"add new view" forState:UIControlStateNormal];
[buttonAddView setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[buttonAddView addTarget:self action:#selector(buttonPushed:) forControlEvents:UIControlEventTouchDown];
NSDictionary *subviews = NSDictionaryOfVariableBindings(textView, buttonAddView);
for (id view in [subviews allValues]) {
[[self view] addSubview:view];
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
}
// initial constraints
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[textView]-|" options:0 metrics:nil views:subviews]];
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[buttonAddView]-|" options:0 metrics:nil views:subviews]];
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[textView(==height)]" options:0 metrics:_metrics views:subviews]];
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[buttonAddView]-|" options:0 metrics:nil views:subviews]];
}
-(void)buttonPushed:(UIButton*)button
{
UITextView *prevView = [_viewList lastObject]; // get reference to previous view
// create a new view
UITextView *newView = [[UITextView alloc] initWithFrame:CGRectNull];
[[self view] addSubview:newView];
[newView setTranslatesAutoresizingMaskIntoConstraints:NO];
[_viewList addObject:newView];
newView.text = [NSString stringWithFormat:#"view: %lu", (unsigned long)[_viewList count]];
NSDictionary *subviews = NSDictionaryOfVariableBindings(prevView, newView);
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[newView]-|" options:0 metrics:nil views:subviews]];
#if 0
// without animation
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[prevView]-space-[newView(==height)]" options:0 metrics:_metrics views:subviews]];
[[self view] layoutIfNeeded];
#else
// with animation
// to begin with the new view gets zero height and no space to previous view
NSArray *tempConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[prevView][newView(==0)]" options:0 metrics:nil views:subviews];
[[self view] addConstraints:tempConstraints];
[[self view] layoutIfNeeded]; // to ensure zero height is the starting point for the animation
[newView setAlpha:0.0f]; // starting point for fade-in
[UIView animateWithDuration:0.25f animations:^{
[[self view] removeConstraints:tempConstraints]; // remove zero height constraint
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[prevView]-space-[newView(==height)]" options:0 metrics:_metrics views:subviews]]; // add final constraints
[newView setAlpha:1.0f]; // fade-in
[[self view] layoutIfNeeded];
}];
#endif
}
#end
You can create a constraint dictionary and a constraint string and apply them programmatically.
Adding items and keys to the dictionary is trivial so I won't deal with that. Just remember every view in the constraint system must be in the dictionary.
Creating the format string is the interesting bit. Presumably you want to add your views under as certain view, say it has the NSString key topView. The first part of the format string looks like
NSString *constraintBase = [NSString stringWithFormat:#"V:topView"];
For each view you want to add, you add to that string
NSString *constraintString = [constraintBase stringByAppendingString:[NSString stringWithFormat:#"-15-%#", viewDictionaryKey]];
Finally apply constraintString as usual, a constructed visual constraint format string.
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:constraintString options:0 metrics:nil views:viewsToConstrain]];
Here the view's which are generated programatically are added to a parent view.
When a view is created its constraints should be added to it.
But the constraints must mapped with the previous view, and so we need to identify the previous view (last view in the parent view)
NSArray *subViewList = [_vwParentView subviews];
UIView *lastView;
if (subViewList.count > 0) {
lastView = [subViewList lastObject];
}
The above code will help to find the last created view.
When Views are created programatically and added as sub views, the views will added as stack for parrentView and hence the view created at last will be the last object in the subViewList array
Note: Assuming that a separate view is assigned as parent view with no subviews initially.
UIView *contentView = [[UIView alloc]init];
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeWidth multiplier:1.0 constant:initialWidth];
[contentView addConstraint:widthConstraint];
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeHeight multiplier:1.0 constant:initialHeight];
[contentView addConstraint:heightConstraint];
contentView.translatesAutoresizingMaskIntoConstraints = NO;
[_vwParentView addSubview:contentView];
NSLayoutConstraint *gapMaintainTopConstraint;
if (lastView == nil) {
gapMaintainTopConstraint = [NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:_vwParentView attribute:NSLayoutAttributeTop multiplier:1.0 constant:15];
}
else
{
gapMaintainTopConstraint = [NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:lastView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:15];
}
[_vwParentView addConstraint:gapMaintainTopConstraint];
In case to change the size of the added view in future, it should be achieved by changing its widthConstraint or heightConstraint, only then the constraint which is associated to it (to maintain specific gap) will work. Size should not be changed using frames thereafter.
Meaning - constraint based views should be handled using constraints only.
It is possible to create a "spacing to nearest neighbour" constraint only after the created view is added as a sub view,.
The translatesAutoresizingMaskIntoConstraints property of the created view should be disabled, so that there won't be any conflict of constraints when there is any change in size (of the created view) in future.

Fixed view width for Portrait and Landscape orientations with Storyboard and Auto Layout?

I'm working on this iPad app and I'm thinking of using auto layout to make my life easier. I created this side bar control that allows users to change to a different page (just like the tab bar controller, but this is along the left side of the iPad). Now in landscape orientation I want the width of this view to be 256 px but when the iPad is in Portrait orientation I want this view to have a width of 100 px. How can I use auto layout to pin the width of the view depending on the interface orientation?
You can use the updateViewConstraints call to modify the layout on orientation change.
The example below creates a view programatically but you could wire up the width constraint of you side bar in interface builder to achieve the same thing.
For example:
//create a custom view using autolayout, this is the equivalent of you side bar
- (void)viewDidLoad{
[super viewDidLoad];
//create a custom view
[self.view setTranslatesAutoresizingMaskIntoConstraints:NO];
UIView *vw=[[UIView alloc] init];
self.customView =vw;
self.customView.backgroundColor = [UIColor blackColor];
[self.customView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:self.customView];
NSArray *arr;
//horizontal constraints
arr = [NSLayoutConstraint constraintsWithVisualFormat:#"|-20-[vw]"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(vw)];
[self.view addConstraints:arr];
//vertical constraints
arr = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-20-[vw(200)]"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(vw)];
[self.view addConstraints:arr];
}
- (void)updateViewConstraints{
[super updateViewConstraints];
//remove the existing contraint
if(self.widthConstraint!=nil){
[self.view removeConstraint:self.widthConstraint];
}
//portait set width to 100
if(UIInterfaceOrientationIsPortrait(self.interfaceOrientation)){
self.widthConstraint = [NSLayoutConstraint constraintWithItem:self.customView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:1.0
constant:100.0];
}
//landscape set width to 256
else{
self.widthConstraint = [NSLayoutConstraint constraintWithItem:self.customView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:1.0
constant:256.0];
}
[self.view addConstraint:self.widthConstraint];
}

Resources