Cant get constraint to pin UIView to bottom of cells contentView - ios

In the cellForRowAtIndexPath: method of a UITableViewController in my application I need to pin a programmatic UIView to the bottom of each cell's respective cell.contentView. Here's my code:
separatorLineView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320,15)];
separatorLineView.tag = 17;
separatorLineView.backgroundColor = [UIColor colorWithHexString:#"F0F5F7"];
[cell.contentView addSubview:separatorLineView];
[self.separatorLineView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSDictionary* views = NSDictionaryOfVariableBindings(separatorLineView);
NSString *format = #"V:[separatorLineView]-|";
positionYConstraint = [NSLayoutConstraint constraintsWithVisualFormat:format
options:0
metrics:nil
views:views];
heightConstraint = [NSLayoutConstraint constraintWithItem:postSeparatorLineView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:cell.contentView
attribute:NSLayoutAttributeHeight
multiplier:0.01
constant:15];
[cell.contentView addConstraints:positionYConstraint];
[cell.contentView addConstraint:heightConstraint];
[cell.contentView layoutSubviews];
When running it with the setTranslatesAuto...:NO line I cannot see the separatorLineView, not even in the view debugger. When I comment that line out, the separatorLineView is set to the top of the cell.contentView.
Essentially all I need the code to do, is pin the separatorLineView to the bottom of each and every cell's contentView - keep in mind that I have dynamic cell heights.

You need constraints on the horizontal, otherwise it won't know its width and x position. Check this:
UIView * separatorLineView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320,15)];
separatorLineView.tag = 17;
separatorLineView.backgroundColor = [UIColor colorWithHexString:#"F0F5F7"]; [cell.contentView addSubview:separatorLineView];
[separatorLineView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSDictionary* views = NSDictionaryOfVariableBindings(separatorLineView);
NSString *formatV = #"V:[separatorLineView(==15)]|"; // changed this line to set 15 as height and to really pin your view (note I remove the "-" because it adds a 8 margin)
NSArray *positionYConstraint = [NSLayoutConstraint constraintsWithVisualFormat:formatV
options:0
metrics:nil
views:views];
NSString *formatH = #"H:|[separatorLineView]|"; //these are the missing constraints
NSArray *positionXConstraint = [NSLayoutConstraint constraintsWithVisualFormat:formatH
options:0
metrics:nil
views:views];
[cell.contentView addConstraints:positionYConstraint];
[cell.contentView addConstraints:positionXConstraint];

Related

Vertical scrolling not working UIScrollView programatically

I am not sure what is wrong with my code. I have embedded UIView inside the UIScrollView. There are some controls which have been embedded inside this UIView. I am expecting the scrolling to happen for the UIView but it does not. Eventually I am unable to view all the controls. I am just pasting my code below. Appreciate if somebody points out what I am missing here.
UIView *parentView = [[UIView alloc]init];
[parentView setTranslatesAutoresizingMaskIntoConstraints:NO];
parentView.backgroundColor = [UIColor whiteColor];
[self.view addSubview: parentView];
NSArray *parentViewHConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:[parentView(==mainView)]" options:0 metrics:0 views:#{#"parentView": parentView, #"mainView":self.view}];
NSArray *parentViewVConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-10-[parentView]-10-|" options:0 metrics:0 views:#{#"parentView": parentView}];
[self.view addConstraints:parentViewHConstraints];
[self.view addConstraints:parentViewVConstraints];
self.myScrollView = [[UIScrollView alloc] init];
//CGSize scrollableSize = CGSizeMake(100, 200);
//[self.myScrollView setContentSize:scrollableSize];
[parentView addSubview:self.myScrollView];
self.myScrollView.backgroundColor = [UIColor lightGrayColor];
self.myScrollView.translatesAutoresizingMaskIntoConstraints = NO;
NSArray *scrollViewHContraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|" options:0 metrics:0 views:#{#"scrollView": self.myScrollView}];
[parentView addConstraints:scrollViewHContraints];
UIView *contentView = [[UIView alloc]init];
contentView.translatesAutoresizingMaskIntoConstraints = NO;
[self.myScrollView addSubview:contentView];
self.myScrollView.showsVerticalScrollIndicator = YES;
self.myScrollView.pagingEnabled = YES;
self.myScrollView.contentSize = CGSizeMake(510,221);
NSArray *contentViewHConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[contentview(==scrollView)]|" options:0 metrics:nil views:#{#"contentview": contentView,#"scrollView": self.myScrollView}];
NSArray *contentViewVConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[contentview(==scrollView)]|" options:0 metrics:nil views:#{#"contentview": contentView,#"scrollView": self.myScrollView}];
[self.myScrollView addConstraints:contentViewHConstraints];
[self.myScrollView addConstraints:contentViewVConstraints];
UIFont *headingFont = [UIFont fontWithName:#"Helvetica Neue" size:40];
self.m_ObjTopHeadingLbl = [[UILabel alloc]init];
[self.m_ObjTopHeadingLbl setText:#"Registration"];
self.m_ObjTopHeadingLbl.font = headingFont;
[self.m_ObjTopHeadingLbl setTextColor:[UIColor redColor]];
self.m_ObjTopHeadingLbl.translatesAutoresizingMaskIntoConstraints = NO;
[contentView addSubview:self.m_ObjTopHeadingLbl];
NSArray *topHeadingHorzConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[registView]-|" options:0 metrics:nil views:#{#"registView": self.m_ObjTopHeadingLbl}];
[contentView addConstraints:topHeadingHorzConstraint];
// Name field
self.m_ObjNameField = [[JVFloatLabeledTextField alloc]init];
self.m_ObjNameField.borderStyle = UITextBorderStyleRoundedRect;
self.m_ObjNameField.translatesAutoresizingMaskIntoConstraints = NO;
self.m_ObjNameField.placeholder = [NSString stringWithFormat:#"Enter your short name"];
[contentView addSubview:self.m_ObjNameField];
NSArray *nameFieldHorzConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[nameFieldView]-|" options:0 metrics:nil views:#{#"nameFieldView": self.m_ObjNameField}];
[contentView addConstraints:nameFieldHorzConstraint];
// Info on the name field
UILabel* nameFieldLbl = [[UILabel alloc]init];
[nameFieldLbl setText:#"Will be visible to all on ipomo"];
UIFont *nameFieldLblFont = [UIFont fontWithName:#"Arial-BoldMT" size:13];
[nameFieldLbl setFont:nameFieldLblFont];
[nameFieldLbl setTextColor:[UIColor grayColor]];
nameFieldLbl.translatesAutoresizingMaskIntoConstraints = NO;
[contentView addSubview:nameFieldLbl];
NSArray *nameFieldlblHorzConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[nameFieldLbl]-|" options:0 metrics:nil views:#{#"nameFieldLbl": nameFieldLbl}];
[contentView addConstraints:nameFieldlblHorzConstraint];
// Mobilenumber field
self.m_ObjMobNo = [[JVFloatLabeledTextField alloc]init];
self.m_ObjMobNo.borderStyle = UITextBorderStyleRoundedRect;
self.m_ObjMobNo.translatesAutoresizingMaskIntoConstraints = NO;
self.m_ObjMobNo.placeholder = [NSString stringWithFormat:#"Enter your mobilenumber"];
[contentView addSubview:self.m_ObjMobNo];
NSArray *mobnoFieldHorzConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[mobnoField]-|" options:0 metrics:nil views:#{#"mobnoField": self.m_ObjMobNo}];
[contentView addConstraints:mobnoFieldHorzConstraint];
// Info on the mobile number field
UILabel* mobnoFieldLbl = [[UILabel alloc]init];
[mobnoFieldLbl setText:#"To send you the activation code. Hidden and secure"];
dispatch_async(dispatch_get_main_queue(), ^{
mobnoFieldLbl.preferredMaxLayoutWidth = self.view.bounds.size.width;
});
UIFont *mobnoFieldLblFont = [UIFont fontWithName:#"Arial-BoldMT" size:13];
[mobnoFieldLbl setFont:mobnoFieldLblFont];
mobnoFieldLbl.numberOfLines = 0;
[mobnoFieldLbl setTextColor:[UIColor grayColor]];
mobnoFieldLbl.translatesAutoresizingMaskIntoConstraints = NO;
[contentView addSubview:mobnoFieldLbl];
NSArray *mobnoFieldlblHorzConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[mobnoFieldlbl]-|" options:0 metrics:nil views:#{#"mobnoFieldlbl": mobnoFieldLbl}];
[contentView addConstraints:mobnoFieldlblHorzConstraint];
// Email id field
self.m_ObjEmailId = [[JVFloatLabeledTextField alloc]init];
self.m_ObjEmailId.borderStyle = UITextBorderStyleRoundedRect;
self.m_ObjEmailId.translatesAutoresizingMaskIntoConstraints = NO;
self.m_ObjEmailId.placeholder = [NSString stringWithFormat:#"Enter your email id"];
[contentView addSubview:self.m_ObjEmailId];
NSArray *emailFieldHorzConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[emailField]-|" options:0 metrics:nil views:#{#"emailField": self.m_ObjEmailId}];
[contentView addConstraints:emailFieldHorzConstraint];
// Info on the email field
UILabel* emailFieldLbl = [[UILabel alloc]init];
[emailFieldLbl setText:#"To send analytics and reports on happenings in you room(s). Hidden and secure"];
emailFieldLbl.numberOfLines = 0;
UIFont *emailFieldLblFont = [UIFont fontWithName:#"Arial-BoldMT" size:13];
dispatch_async(dispatch_get_main_queue(), ^{
emailFieldLbl.preferredMaxLayoutWidth = self.view.bounds.size.width;
});
[emailFieldLbl setFont:emailFieldLblFont];
[emailFieldLbl setTextColor:[UIColor grayColor]];
emailFieldLbl.translatesAutoresizingMaskIntoConstraints = NO;
[contentView addSubview:emailFieldLbl];
NSArray *emailFieldlblHorzConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[emailFieldlbl]-|" options:0 metrics:nil views:#{#"emailFieldlbl": emailFieldLbl}];
[contentView addConstraints:emailFieldlblHorzConstraint];
// Promo code field
self.m_ObjPromoCode = [[JVFloatLabeledTextField alloc]init];
self.m_ObjPromoCode.borderStyle = UITextBorderStyleRoundedRect;
self.m_ObjPromoCode.translatesAutoresizingMaskIntoConstraints = NO;
self.m_ObjPromoCode.placeholder = [NSString stringWithFormat:#"Enter promocode (if applicable)"];
[contentView addSubview:self.m_ObjPromoCode];
NSArray *promocodeFieldHorzConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[promocodeField]-|" options:0 metrics:nil views:#{#"promocodeField": self.m_ObjPromoCode}];
[contentView addConstraints:promocodeFieldHorzConstraint];
// Submit button
self.m_ObjSubmitBut = [[UIButton alloc]init];
[self.m_ObjSubmitBut setTitle: [NSString stringWithFormat:#"SUBMIT"] forState:UIControlStateNormal];
self.m_ObjSubmitBut.backgroundColor = [UIColor redColor];
[self.m_ObjSubmitBut setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
self.m_ObjSubmitBut.translatesAutoresizingMaskIntoConstraints = NO;
self.m_ObjSubmitBut.layer.cornerRadius = 10;
self.m_ObjSubmitBut.clipsToBounds = YES;
[parentView addSubview:self.m_ObjSubmitBut];
NSDictionary *myTopViews = #{
#"scrollView": self.myScrollView,
#"submitButton": self.m_ObjSubmitBut,
};
NSArray *myTopVConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]-[submitButton(40)]|" options:0 metrics:nil views:myTopViews];
NSArray *myTopHConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[submitButton]-|" options:0 metrics:nil views:#{#"submitButton": self.m_ObjSubmitBut}];
[parentView addConstraints:myTopVConstraints];
[parentView addConstraints:myTopHConstraints];
NSDictionary* myViews = #{
#"registView": self.m_ObjTopHeadingLbl,
#"nameView": self.m_ObjNameField,
#"nameFieldLbl": nameFieldLbl,
#"mobnoView":self.m_ObjMobNo,
#"mobnoFieldLbl":mobnoFieldLbl,
#"emailView":self.m_ObjEmailId,
#"emailFieldLbl":emailFieldLbl,
#"promocodeView":self.m_ObjPromoCode
};
NSDictionary* myMetrics = #{
#"sepHeight" : #30.0f,
#"sepHeight1" : #5.0f
};
NSArray *otherConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[registView]-sepHeight-[nameView]-sepHeight1-[nameFieldLbl]-sepHeight-[mobnoView]-sepHeight1-[mobnoFieldLbl]-sepHeight-[emailView]-sepHeight1-[emailFieldLbl]-sepHeight-[promocodeView]" options:0 metrics:myMetrics views:myViews];
[contentView addConstraints:otherConstraints];
You're setting the size of your contentview to be equal to the size of the containing scrollview. Logically this means the scrollview has no area to scroll to and therefore will be fixed. You need to make the contentview longer (for vertical scrolling) or wider (for horizontal scrolling).
I think you are missing to mention scrollEnabled of UIScrollView, try following line and hope it works for you.
scrollView.scrollEnabled = YES;
Also set contentSize and its size should be greater than size of containing window to allow it to be scrollable.
[self.scrollforImage setContentSize:CGSizeMake(3000, 3000)];// replace 3000, 3000 according to your need
The constraints for the scrollview in your code are not sufficient. When using auto-layout, ensure that a proper chain of constraints are added in the vertical and horizontal directions.
For a scroll view to scroll, these are some general guidelines,
Ensure all the different sections of the UI have sufficient number of vertical and horizontal constraints. Take a simple UILabel in the screen. It needs a x,y position - can be a leading/top, centre attribute and it needs a size definition - either fixed width and height or a constraint with the nearby elements. It needs the position - size combination clearly defined for it to be managed by auto-layout.
If you set the translatesAutoresizingMaskIntoConstraints to NO, what you are saying is that the component shouldn't take the frame,size already set to be constraint requirements. If you want to explicitly mention frame or size for some elements, leave this as YES.
If the contentview is pinned in all four sides to the scrollview and the scrollview has its size,position defined as constraints or frame, it will be enough to make the content scrollable.
If the content size of the contentview changes dynamically during different events, you can set the scrollview.contentsize to the contentview.width and contentview.height explicitly.
Also, check this, a technical note from Apple on AutoLayout and Scrollviews.

What auto layout constraints lets a subview to compress to 0 size when the parent's size decreases?

Trying to figure out what constraints do I need to add to a view-subview layout hierarchy that would allow the subview to auto resize when the parent view's size changes. Not using interface builder intentionally, translatesAutoresizingMaskIntoConstraints is set to NO for all participating views.
Below is a code snippet that tries to describe the situation:
UIView *canvas = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
UIView *container = [[UIView alloc] initWithFrame:CGRectZero];
container.translatesAutoresizingMaskIntoConstraints = NO;
[canvas addSubview:container];
NSNumber *height = #(50);
NSNumber *margin = #(10);
NSDictionary *metrics = NSDictionaryOfVariableBindings(height, margin);
NSDictionary *views = NSDictionaryOfVariableBindings(container);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(margin)-[container(height)]" options:0 metrics:metrics views:views];
[canvas addConstraints:constraints];
NSLayoutConstraint *containerHeightConstraint = [constraints objectAtIndex:1]; // Know this is unsafe and shouldn't use the visual format in this case, but visual format serves SO purposes better
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-(margin)-[container]-(margin)-|" options:0 metrics:nil views:views];
[canvas addConstraints:constraints];
UIView *subView = [[UIView alloc] initWithFrame:CGRectZero];
subView.translatesAutoresizingMaskIntoConstraints = NO;
[container addSubview:subView];
margin = #(5);
metrics = NSDictionaryOfVariableBindings(margin);
views = NSDictionaryOfVariableBindings(subView);
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(margin)-[subView]-(margin)-|" options:0 metrics:metrics views:views];
[container addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-(margin)-[subView]-(margin)-|" options:0 metrics:metrics views:views];
[container addConstraints:constraints];
[container layoutIfNeeded]; // Layout before animation
containerHeightConstraint.constant = 0;
[UIView animateWithDuration:animationDuration animations:^{
[container layoutIfNeeded]; // Animate container to 0 height
} completion:nil];
The question is what constraints do I need to add that would mimic the autoresize mask constraints that Interface Builder creates and would allow subView in the above example to shrink to 0 height as container shrinks.
If you could also explain the mechanics of such constraints that would help me understand Auto Layout better.
All you need is to set the height and width constant constrain to your subview when the container resizes.

How to add constraints programatically to place a label at the left bottom of the screen?

I want to place a label at the bottom left of the screen: So I added the following code:
UILabel *versionLabel = [UILabel new];
versionLabel.text = #"some text";
versionLabel.textColor = [UIColor whiteColor];
versionLabel.font = [UIFont systemFontOfSize:10.0];
[self.view addSubview:versionLabel];
[versionLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
NSDictionary *views = NSDictionaryOfVariableBindings(versionLabel);
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|-3-[versionLabel]"
options:0
metrics:nil
views:views]];
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:[versionLabel]-3-|"
options:0
metrics:nil
views:views]];
My issue is that the label ends up at the top left always. No way to move it at the bottom when I add constraints.
If you wrap the UITableViewController in a UIViewController, you can layout the label and table view as you see fit.
#import "WrappingViewController.h"
#import "YourCustomTableViewController.h"
#implementation WrappingViewController {
}
- (void)viewDidLoad {
// I instantiate this manually, but you could use an outlet from interface builder
UITableViewController *tableViewController = [YourCustomTableViewController new];
[self addChildViewController:tableViewController];
UIView *tableView = tableViewController.view;
UILabel *label1 = [UILabel new];
label1.text = #"Some text";
label1.font = [UIFont systemFontOfSize:10.0];
label1.translatesAutoresizingMaskIntoConstraints = NO;
tableView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:tableView];
[self.view addSubview:label1];
NSDictionary *views = NSDictionaryOfVariableBindings(tableView, label1);
[self.view addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|[tableView]|" // make the tableview take the whole width
options:0
metrics:nil
views:views]
];
[self.view addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|-3-[label1]" // attach the label 3 pixels from the left
options:0
metrics:nil
views:views]
];
[self.view addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[tableView]-[label1]-3-|" // attach tableview to top, then space, then label, 3px, then bottom
options:0
metrics:nil
views:views]
];
}
#end

Center views using *only* visual format?

I'm trying to center 2 views with different height vertically.
UILabel *view1 = [[UILabel alloc] init];
view1.backgroundColor = [UIColor redColor];
view1.text = #"view1\nline2";
view1.textAlignment = NSTextAlignmentCenter;
view1.numberOfLines = 0;
UILabel *view2 = [[UILabel alloc] init];
view2.backgroundColor = [UIColor greenColor];
view2.text = #"view2\nline2";
view2.textAlignment = NSTextAlignmentCenter;
view2.numberOfLines = 0;
[self.view addSubview:view1];
[self.view addSubview:view2];
NSDictionary *views = #{#"view1": view1, #"view2" : view2};
[self.view setTranslatesAutoresizingMaskIntoConstraints:NO];
for (UIView *view in views.allValues) {
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
}
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[view1]-[view2(==view1)]-|"
options:NSLayoutFormatAlignAllCenterY
metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(>=20)-[view1(200)]-(>=20)-|"
options:NSLayoutFormatAlignAllCenterY
metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(>=20)-[view2(100)]-(>=20)-|"
options:NSLayoutFormatAlignAllCenterY
metrics:nil views:views]];
This manages to make the centers of both aligned vertically, but they are at the bottom of the superview!
I want to center vertically not only relative to each other, also in the superview.
I added a constraint like this and it works:
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1
constant:0]];
But I want to know, is this possible to achieve using only with visual format?
Yes, it's possible, but only by adding spacer views. So if you create a couple of views, lets call them spacer1 and spacer2, you can center view1 with this string,
#"V:|[spacer1][view1][spacer2(==spacer1)]|"
This assumes that view1 has a fixed height. I wouldn't recommend doing it this way though, I would just use the code you show at the bottom of your post.
What about calculating the Y in "metrics" ?
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-centerY-[view1(200)]-(>=20)-|"
options:NSLayoutFormatAlignAllCenterY
metrics:#{#"centerY": #(CGRectGetHeight(self.view.frame)/2.0 - 100)}
views:views]];

Auto Layout on UITableView header

I've been searching for a clear answer on how to add auto layout to a UITableView. So far, my code looks like:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UINib *nib = [UINib nibWithNibName:#"HomeHeaderView" bundle:nil];
UIView *headerView = (UIView *)[nib instantiateWithOwner:self options:nil][0];
[headerView.layer setCornerRadius:6.0];
[headerView setTranslatesAutoresizingMaskIntoConstraints:NO];
// NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(headerView);
// NSMutableArray *headerConstraints = [[NSMutableArray alloc] init];
// [headerConstraints addObject:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[headerView]-|" options:0 metrics:nil views:viewsDictionary]];
// [headerConstraints addObject:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[headerView]-|" options:0 metrics:nil views:viewsDictionary]];
// [self.actionsTableView addConstraints:headerConstraints];
// [self.view addSubview:headerView];
tableView.tableHeaderView = headerView;
[headerView layoutSubviews];
NSLayoutConstraint *centerX = [NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0];
NSLayoutConstraint *centerY = [NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1 constant:300];
NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1 constant:90];
[self.view addConstraints:#[centerX, centerY, width, height]];
return headerView;
}
I basically have a nib file for my header view and I want to center that nib in my UITableViewHeader. I'd like it to grow and shrink accordingly in portrait and landscape orientations. I'm honestly unsure if I set up the constraint properly. I was not sure if my toItem was supposed to be the view controller's view, or the tableview itself.
I also did not know if I was supposed to add the headerview as a subview to either the view controller's view, or the tableview itself.
Or, I wasn't sure if setting tableView.tableHeaderView = headerView was enough.
I really have no clue what the best practices are for something like this. I wasn't sure if it all could be done in IB as well. Currently, with the code you see, I get this error:
'Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super.'
It's because of that error, that I added [headerView layoutSubviews]
Thoughts on this? Thanks in advance!
The real problem is that you've confused viewForHeaderInSection: with the table's headerView. They are unrelated.
The former is the header for a section. You return the view from the delegate method.
That latter is the header for the table. You set the view, probably in your viewDidLoad.
Constraints operate in the normal way. But they should only be internal constraints to their subviews. At the time you form it, the view is not in your interface. And its size and place are not up to you at that time. If it's the section header, it will be resized automatically to fit correctly (in accordance with the table's width and the table's or delegate's statement of the header height). If it's the table header, you can give it an absolute height, but its width will be resized to fit correctly.
Here is a complete example of constructing a section header with internal constraints on its subviews.
- (UIView *)tableView:(UITableView *)tableView
viewForHeaderInSection:(NSInteger)section {
UITableViewHeaderFooterView* h =
[tableView dequeueReusableHeaderFooterViewWithIdentifier:#"Header"];
if (![h.tintColor isEqual: [UIColor redColor]]) {
h.tintColor = [UIColor redColor];
h.backgroundView = [UIView new];
h.backgroundView.backgroundColor = [UIColor blackColor];
UILabel* lab = [UILabel new];
lab.tag = 1;
lab.font = [UIFont fontWithName:#"Georgia-Bold" size:22];
lab.textColor = [UIColor greenColor];
lab.backgroundColor = [UIColor clearColor];
[h.contentView addSubview:lab];
UIImageView* v = [UIImageView new];
v.tag = 2;
v.backgroundColor = [UIColor blackColor];
v.image = [UIImage imageNamed:#"us_flag_small.gif"];
[h.contentView addSubview:v];
lab.translatesAutoresizingMaskIntoConstraints = NO;
v.translatesAutoresizingMaskIntoConstraints = NO;
[h.contentView addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|-5-[lab(25)]-10-[v(40)]"
options:0 metrics:nil views:#{#"v":v, #"lab":lab}]];
[h.contentView addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[v]|"
options:0 metrics:nil views:#{#"v":v}]];
[h.contentView addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[lab]|"
options:0 metrics:nil views:#{#"lab":lab}]];
}
UILabel* lab = (UILabel*)[h.contentView viewWithTag:1];
lab.text = self.sectionNames[section];
return h;
}
I found that solution provided by matt might not be the perfect, because he's adding custom views and constraints to UITableViewHeaderFooterView's contentView. That is always causing Auto Layout warnings in runtime: Unable to simultaneously satisfy constraints when we want to have dynamic header height.
I am not sure about the reason, but we can assume that iOS adds some extra constrains to contentView that sets fixed width and height of that view. Warnings generated in runtime tells that constraints we added manually can't be satisfied with those, and it's obvious because our constraints should stretch header view so the subviews can fit in it.
Solution is pretty easy - don't use UITableViewHeaderFooterView's contentView, just add your subviews directly to UITableViewHeaderFooterView. I can confirm that it's working without any issues on iOS 8.1. If you want to add several views and change the background color of you header, consider adding UIView that fills header view (thanks to AutoLayout constraints) and then all the subviews you would like to have to that view (I am calling it customContentView). That way we can avoid any AutoLayout issues and have auto-sizing headers in UITableView.
This is a neat solution:
Optional: initWithStyle:UITableViewStyleGrouped to prevent floating tableViewHeader
Make two properties, the label is just for demonstration:
#property (nonatomic, strong, readwrite) UIView *headerView;
#property (nonatomic, strong, readwrite) UILabel *headerLabel;
Setup everything in viewDidLoad:
self.headerView = [[UIView alloc] initWithFrame:CGRectZero];
self.headerLabel = [[UILabel alloc] init];
self.headerLabel.text = #"Test";
self.headerLabel.numberOfLines = 0; //unlimited
self.headerLabel.textAlignment = NSTextAlignmentCenter;
self.headerLabel.translatesAutoresizingMaskIntoConstraints = NO; //always set this to NO when using AutoLayout
[self.headerView addSubview:self.headerLabel];
NSString *horizontalFormat = #"H:|-[headerLabel]-|";
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:horizontalFormat options:0 metrics:nil views:#{#"headerLabel":self.headerLabel}];
[self.headerView addConstraints:horizontalConstraints];
NSString *verticalFormat = #"V:|-[headerLabel]-|";
NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:verticalFormat options:0 metrics:nil views:#{#"headerLabel":self.headerLabel}];
[self.headerView addConstraints:verticalConstraints];
In viewForHeaderInSection:
return self.headerView;
In heightForHeaderInSection:
self.headerLabel.preferredMaxLayoutWidth = tableView.bounds.size.width;
return [self.headerView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

Resources