I am having a strange problem with UIBarButtonItem. I am creating one with a custom view, and the view is of my own MyCustomView type. It contains couple of labels and some other subviews. Whenever I use autolayout in this custom view class to lay out the subviews - the button is displayed on top-left corner of the screen! You can see it in the picture below - MyCustomView just has one subview with gray background and I use autolayout to stretch it to fill the parent MyCustomView:
When I don't use autolayout everything is fine and the button is displayed normally:
Can anyone explain me what is going on here and am I allowed to use autolayout in custom views which are going to be put in UIBarButtonItems (maybe not on the topmost level) ?
UPDATE: this happens after I do
self.translatesAutoresizingMaskIntoConstraints = NO;
in MyCustomView - in the top view which should be placed in bar button. I am doing it to make the system call my intrinsicContentSize method. I guess using the old sizeThatFits: instead would still be ok.
UPDATE 2: Here is the test code:
UIControl *cus = [[UIControl alloc] initWithFrame:CGRectMake(0, 0, 22, 22)];
// uncomment the following line to mess everything
//cus.translatesAutoresizingMaskIntoConstraints = NO;
cus.backgroundColor = [UIColor redColor];
UILabel *label = [UILabel new];
label.translatesAutoresizingMaskIntoConstraints = NO;
label.text = #"Q";
[label sizeToFit];
label.font = [UIFont systemFontOfSize:18];
[cus addSubview:label];
NSLayoutConstraint *centerX1 = [NSLayoutConstraint constraintWithItem:cus
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:label
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0];
NSLayoutConstraint *centerY1 = [NSLayoutConstraint constraintWithItem:cus
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:label
attribute:NSLayoutAttributeCenterY
multiplier:1
constant:0];
[cus addConstraints:#[centerX1, centerY1]];
UIBarButtonItem *barButton = [[UIBarButtonItem alloc] initWithCustomView:cus];
controller.navigationItem.rightBarButtonItem = barButton;
I would be happy to get explanations to understand what is going on.
Related
I'm quite surprised. I didn't find anything remotely tackling the issue on SO. I would have imagined that many people may have tried that before me. That must only mean one thing ... It's trivial and I can't make it :(
How do I add constraints to the cameraOverlay ?
I tried this
UIView *overlayView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, screenWidth, screenHeight)];
[overlayView setBackgroundColor:[UIColor redColor]];
[overlayView setAlpha:0.5];
UILabel *Testlabel = [[UILabel alloc] init];
[Testlabel setText:#"Hollllaaaaa in the overlay"];
[overlayView addSubview:Testlabel];
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:overlayView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:Testlabel
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0];
[overlayView addConstraints:#[widthConstraint]];
[self.picker setCameraOverlayView:overlayView];
But I get the error Unable to simultaneously satisfy constraints. With just one constraint.
As a note, the picker is part of a view that has constraints and are well defined. I am wondering if the fact that I initwithFrame the overlay view has something to do with autolayout not liking that ... If that the case, how can I simply add a constraint to a blank overlay that would have the same size as the screen ?
i am trying to add button inside the scrollview with center constrain,
constrain working but scroll not working scroll become stuck,
Anyone can help me where is mistake,
-(void)viewDidLoad{
scrllview = [[UIScrollView alloc] initWithFrame:
[[UIScreen mainScreen] applicationFrame]];
scrllview.backgroundColor = [UIColor orangeColor];
self.view=scrllview;
[scrllview setContentSize:CGSizeMake(300, 1000)];
submitButton = [UIButton buttonWithType:UIButtonTypeCustom];
[submitButton setTitle:#"connect" forState:UIControlStateNormal];
[submitButton.titleLabel setFont:[UIFont fontWithName:#"HelveticaNeue-Bold" size:14.0]];
[submitButton addTarget:self
action:#selector(myMethod:)
forControlEvents:UIControlEventTouchUpInside];
submitButton.backgroundColor = [UIColor blackColor];
submitButton.translatesAutoresizingMaskIntoConstraints = NO ;
[scrllview addSubview:submitButton];
NSLayoutConstraint *constraint = [NSLayoutConstraint
constraintWithItem:submitButton
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:scrllview
attribute:NSLayoutAttributeCenterX
multiplier:1.0f
constant:0.0f];
[scrllview addConstraint:constraint];
constraint = [NSLayoutConstraint
constraintWithItem:submitButton
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:scrllview
attribute:NSLayoutAttributeCenterY
multiplier:1.0f
constant:0.0f];
[scrllview addConstraint:constraint];
}
While you are using scrollview with autolayout, you should not use contentsize.
You can create a view inside scroll view, which should have your required height and constraint relative to scrollview. You can add your button inside that view.
It will work. I have implemented it with xib. Same issue while adding control directly in scrollview. I used view inside scrollview and added required control to view.
Auto layout UIScrollView with subviews with dynamic heights
In my table view's cellForRowAtIndexPath: method I have the following code to add an image and align it in the cell:
UIImageView *PocketIcon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"pocket-icon"]];
[cell addSubview:PocketIcon];
NSLayoutConstraint *iconDistanceFromCellTopConstraint = [NSLayoutConstraint constraintWithItem:PocketIcon attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:cell attribute:NSLayoutAttributeTop multiplier:1.0 constant:14.0];
[cell addConstraint:iconDistanceFromCellTopConstraint];
NSLayoutConstraint *iconDistanceFromCellLeftConstraint = [NSLayoutConstraint constraintWithItem:PocketIcon attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:cell attribute:NSLayoutAttributeLeft multiplier:1.0 constant:22.0];
[cell addConstraint:iconDistanceFromCellLeftConstraint];
However each time the image does indeed get added, but it just sits in the top left corner of the cell. What's wrong with the above code that's causing the constraint not to work?
Your code works for me after setting translatesAutoresizingMaskIntoConstraints to NO:
UIImageView *PocketIcon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"pocket-icon"]];
PocketIcon.translatesAutoresizingMaskIntoConstraints = NO;
[cell addSubview:PocketIcon];
Another little advice I'd like to give. I use constrains extensively and my life became much easier after I started using category for working with constraints, this one:
https://github.com/PureLayout/PureLayout
I suggest you try it too.
So add your imageView and constraints to cell.contentView not cell ( [cell.contentView addSubview:PocketIcon];). Also you want to turn off AutoresizingMask, so add this[PocketIcon setTranslatesAutoresizingMaskIntoConstraints:NO] . You may need a bool to make sure not to add the constraints more then once as the table is scrolled.
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;
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.
I did it this way in a recent project to add a label and a UIActivityIndicatorView:
-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView *headerView = [self.tableView dequeueReusableHeaderFooterViewWithIdentifier:#"Header"];
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(0,0, 250, 20)];
[label setTranslatesAutoresizingMaskIntoConstraints:NO];
NSLayoutConstraint *con1 = [NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeCenterY relatedBy:0 toItem:label attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
NSLayoutConstraint *con2 = [NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeLeft relatedBy:0 toItem:label attribute:NSLayoutAttributeLeading multiplier:1 constant:-10];
[headerView addConstraints:#[con1,con2]];
label.text = #"Pick an Activity from the List";
label.backgroundColor = [UIColor clearColor];
[headerView addSubview:label];
spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[spinner setTranslatesAutoresizingMaskIntoConstraints:NO];
if (activityIndicatorShouldStop == NO) [spinner startAnimating];
[headerView addSubview:spinner];
NSLayoutConstraint *con3 = [NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeCenterY relatedBy:0 toItem:spinner attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
NSLayoutConstraint *con4 = [NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeRight relatedBy:0 toItem:spinner attribute:NSLayoutAttributeTrailing multiplier:1 constant:10];
[headerView addConstraints:#[con3,con4]];
return headerView;
}
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.