Coyote6 Coyote6 - 21 days ago 12
iOS Question

iOS add constraint to table section header

I am working with a grouped table, and am customizing the header in the sections using tableView: viewForHeaderInSection: method and setting the height using tableView: heightForHeaderInSection:.

I created a view and placed a label in it like so:

- (UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {


// Create a custom title view.
UIView *ctv;
UILabel *titleLabel;

// Set the name.
{...} // Code not relevant

// If an iPhone.
if ([Config isPhone]) {
ctv = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 50)];
titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 14, 320, 36)];
}
// If an iPad
else {
ctv = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 768, 75)];
titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 20, 544, 55)];
}

// Config the label.
{...} // Code not relevant

// Center the items.
ctv.center = CGPointMake(tableView.center.x, ctv.center.y);
titleLabel.center = ctv.center;

// Add the label to the container view.
[ctv addSubview:titleLabel];

// Return the custom title view.
return ctv;


}


This all works great until you rotate the screen. The position is off. I realize that this is because the view is being centered while it is in the other orientation causing the calculation of the center to no longer be correct. The solution should be to add a constraint. I tried adding the constraint below:

NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(ctv);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|[ctv]|"
options:0
metrics:nil
views:viewsDictionary
];
[tableView addConstraints:constraints];


But when I do this trying the method below, I get that no parent view is associated with it, which makes complete sense, because it doesn't technically get added into the view is returned. So I thought I would try to add the constraint this way:

NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:ctv
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:tableView
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0
];
[tableView addConstraint:constraint];


But this one also errors. I've tried switching the tableView variables to the global table property but it gives the same results. I also tried to figure out how to add the constraint in the view did load method but it failed as I could not figure out how to get back to the table's section headers from the table object. The last thought I had was to set the width on the table in a constraint and set one to center the entire table. This process worked but now I have the an ugly scroll in the middle of my app when it is in the landscape orientation. So the question is, where/how can I access the individual section headers after they have been loaded to add this constraint? I'm still pretty new to Objective-C so any help is appreciated.

***** NEW CODE BASED FROM rdelmar SUGGESTION ****

- (UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView *ctv = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"groupHeader"];
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 20, 544, 55)];
[titleLabel setTranslatesAutoresizingMaskIntoConstraints: NO];
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:ctv
attribute:NSLayoutAttributeCenterX
relatedBy:0
toItem:titleLabel
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0
];
[ctv addConstraints:@[constraint]];
titleLabel.text = @"string";
[ctv addSubview:titleLabel];
return ctv;
}


But like I mentioned, it is giving me a "Constraint must contain a first layout item" error.

Answer

If you can't get the constraints to work, your original code supplemented by an autoresizing mask (flexible left and right margins) would do the job.

An even simpler solution would be to return a UILabel as the header view, with centered text.

Your first attempt at constraints wouldn't work because you are setting them up wrong.

The table view is responsible for setting the frame of the header view. You need to worry about the position of the label within the header. The VFL for this would be "|titleLabel|" - the title label should be sized to its superview, the header view.