Why is this constraint not working? - ios

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.

Related

iPhone X TableView Footer problems

Ok, two things first: I'm still new to iOS development and I bet this question was already answered a few times, so feel free to link it to a working solution 😊
Now to the problem: I have a UITableViewController including custom footer views. (They are custom to add a little border on the bottom, the build-in footer is just plain grey). Everything looks ok for the old school iPhones, however on the new X ones I get the following:
Is there a way to extend the footer view all down to the bottom of the view area?
The default layout is like this because of safe area. If you want to let the footer view cover the content I can give you 2 solutions.
Use UITableViewStyleGrouped instead, but the footer view will not static on the screen.
I don't think this is the best practise, but I think the layout should be you are looking for. This is a little bit tricky. The step is:
Create an extend view that the height equal to the bottom safe area.
Put this extend view below the footer view and set the background color same as header view's background color.
Make sure the header view height equals to the bottom safe area height. (This step is the tricky point)
Sample code
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
UILabel *footerView = [[UILabel alloc] init];
footerView.backgroundColor = [UIColor colorWithRed:0.968 green:0.968 blue:0.968 alpha:1]; //Section Header Background Color
footerView.textAlignment = NSTextAlignmentRight;
footerView.text = #"Footer";
UIView *extendView = [[UIView alloc] init];
extendView.translatesAutoresizingMaskIntoConstraints = NO;
extendView.backgroundColor = footerView.backgroundColor;
[footerView addSubview:extendView];
[footerView addConstraints:#[
[NSLayoutConstraint constraintWithItem:extendView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:footerView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0],
[NSLayoutConstraint constraintWithItem:extendView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:footerView
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0],
[NSLayoutConstraint constraintWithItem:extendView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:footerView
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0],
[NSLayoutConstraint constraintWithItem:extendView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:footerView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:self.view.safeAreaInsets.bottom]
]];
return footerView;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return self.view.safeAreaInsets.bottom;
}
Screen Record GIF

How to add autolayout constraints to a multi-line UILabel inside tableview headerView?

I have searched all day for a solution to this problem. I am trying to have my UILabels autosize themselves based on the length of the text inside a tableViewHeaderView. Normally, with my UILabels inside a UIView, I would set top, leading, and trailing constraints to the UILabel and it would work just like how I wanted. However, I can't get that working inside a tableViewHeaderView. I am able to set top and leading constraints but my trailing constraint doesnt seem to be working. The text goes beyond the width of the screen.
Setting the preferredMaxLayoutWidth property to a number solves the issue but I don't want to have to hard code that.
Correct me if I am wrong, but setting the leading and trailing constraints should be able to give me the width of the view does it not? Then I could set preferredMaxLayoutWidth with that value. But that value is 2403 which is way longer than the width of the screen.
Anyone else experience this?
#import "CustomTableViewHeader.h"
#implementation ReplyHeader{
UILabel *questionLabel;
}
questionLabel = [[UILabel alloc]init];
questionLabel.text = #"SAMPLE TEXT: I had a question about how I can be a better human being using your method. I belive it is an integral part of what it means to be a human so I want to learn more if you are able give more details about it. I also found that what you said about the dogs out there is very cool and would love to learn more about that.";
questionLabel.font = [UIFont fontWithName:#"AvenirNext-Regular" size:17];
questionLabel.lineBreakMode = NSLineBreakByWordWrapping;
questionLabel.numberOfLines = 0;
questionLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:questionLabel];
Constraints
[self addConstraint:[NSLayoutConstraint constraintWithItem:questionLabel attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeading multiplier:1.0f constant:5.0f]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:questionLabel attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTrailing multiplier:1.0f constant:-5.0f]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:questionLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0f constant:10]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:questionLabel attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1.0f constant:-10]];
update view
- (void)viewDidLayoutSubviews
{
questionLabel.preferredMaxLayoutWidth = questionLabel.frame.size.width;
[self layoutIfNeeded];
}
UITableView's do not get along with AutoLayout. Both header and footer have inherit that problem.
As you might already know, UITableViews are fancy UIScrollViews. If you have ever tried to work with a scroll view, lots of elements and AutoLayout you must know that it does not work well. It lags. That's why UITableView reuse cells. It improves performance and they do not use AutoLayout.
You should try something like
// with this you will get the most compressed size for your view if the constraints properly define it's size
CGFloat height = [footer systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
footer = CGRectMake(CGRectGetMinX(view.frame), CGRectGetMinY(footer.frame), CGRectGetWidth(footer.bounds), height);
// This will tell the layout engine to ignore the view and not try to resize it
view.autoresizingMask = UIViewAutoresizingNone;
self.tableView.tableFooterView = footer;

how to add multiple images with each of its constraints using For/While loop

What i want to achieve is set buttons/images Horizontally Centered with main view, including its width set to 75% of the screen width.
I want to fit like 7 such images/buttons vertically on the screen (line-by-line).
I am using the following code, which is working perfect:
UIImageView *l1 = [[UIImageView alloc] initWithFrame:CGRectMake(20, 20, 100, 100)];
[l1 setImage:[UIImage imageNamed:#"level-1"]];
[l1 setContentMode:UIViewContentModeScaleAspectFit];
l1.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:l1];
NSLayoutConstraint *c1 = [NSLayoutConstraint
constraintWithItem:l1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:20.f
];
[self.view addConstraint:c1];
NSLayoutConstraint *c1b = [NSLayoutConstraint
constraintWithItem:l1
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0f
constant:0.f
];
[self.view addConstraint:c1b];
NSLayoutConstraint *c1c = [NSLayoutConstraint
constraintWithItem:l1
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:0.75f
constant:0.f
];
[self.view addConstraint:c1c];
Instead of repeating the same code for different images, i want to use some iteration process and increment the image name [UIImage imageNamed:#"level-xxx"]] and bind top position to the bottom position of the last added item.
How it would be possible? Thx
iOS 9 introduces a new class called UIStackView that allows you to do this :
https://www.hackingwithswift.com/read/31/2/uistackview-by-example
There are even back-ports of this to iOS 7 on Github.
Some people might downvote me for this but sometimes , when it comes to autolayout I just say , $##& this I'll write the layout code myself. And this is one of those cases (especially when UIStackView is not available).
First create all your views and store them in an array in viewDidLoad.
Override viewDidLayoutSubviews on your view controller and loop through the array , set the frames and lay them out one by one.
-(void)viewDidLayoutSubviews
{
NSArray* imageList = self.imageViewList
// calculate it if necessary
CGPoint startPoint = CGPointMake(200,200)
CGFloat spacing = 50
for(UIView *view in imageList) {
view.center = startPoint
startPoint = CGPointMake(startPoint.x , startPoint.y + spacing)
}
}
This is way more obvious , way easier to debug than a mess of constraints created in code.

Custom cell separator lines disappear upon scrolling

I have implemented custom cell separator lines for my static table view cells by creating a view with a gray background, and I need to apply Autolayout constraints so that the lines will resize to fill the display width upon rotating to different orientations. I have done just that with the code below, provided from another SO question I asked. This is working great, except there's one problem. When I scroll down and then back up, these custom separator lines disappear. Selecting the cell causes them to appear again, but they will soon disappear again after scrolling.
I know this is caused by the Autolayout constraints, because previous Autolayout constraints I added did not cause this issue to occur. If you check out that linked question you'll learn I experienced several issues with these custom lines and was able to solve them all, except for this disappearing issue. If you could help me solve this issue I'd surely appreciate it.
This is exactly how I set up my separator lines - I do this in viewDidLoad:
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[cell.contentView addSubview:imageView];
imageView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(imageView);
//Add a line separator above a cell:
//these constraints ensure the separator line is visible above the cell's accessory view
[cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"H:|-(%f#750)-[imageView]-(-%f#750)-|", indent, self.tableView.rowHeight] options:0 metrics:0 views:viewsDictionary]];
[cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"V:[imageView(0.5)]-(%f#750)-|", self.tableView.rowHeight] options:0 metrics:0 views:viewsDictionary]];
My question is, why do these Autolayout constraints cause the views to disappear, and how can I tweak them to ensure they will always remain visible?
I found some constraints that have solved these problems:
[cell addConstraint:[NSLayoutConstraint constraintWithItem:imageView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:cell
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0]];
[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]];

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]];

Resources