Custom cell separator lines disappear upon scrolling - ios

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

Related

how to do uilabel programmatically in auto layout

I am doing a project in auto layout in Xcode 6, I am adding a label programmatically its working perfectly in 4s,5,5s but in 6 and 6 plus is not working. can anyone help me, I am new to auto layout.Below is my coding.
UIScrollView *scroll = [[UIScrollView alloc]init];
[scroll setTranslatesAutoresizingMaskIntoConstraints:NO];
scroll.contentSize=CGSizeMake(480, 600);
[centerView addSubview:scroll];
NSDictionary *scrolldic = #{#"scrollview":scroll};
NSArray *scroll_H = [NSLayoutConstraint constraintsWithVisualFormat:#"H:[scrollview(480)]" options:0 metrics:Nil views:scrolldic];
NSArray *scroll_V = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[scrollview(480)]" options:0 metrics:Nil views:scrolldic];
[scroll addConstraints:scroll_H];
[scroll addConstraints:scroll_V];
NSArray *scroll_posH = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[scrollview]" options:0 metrics:Nil views:scrolldic];
NSArray *scroll_posV = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[scrollview]" options:0 metrics:Nil views:scrolldic];
[self.view addConstraints:scroll_posH];
[self.view addConstraints:scroll_posV];
UILabel *header = [[UILabel alloc]init];
[header setTranslatesAutoresizingMaskIntoConstraints:NO];
header.backgroundColor = [UIColor blueColor];
[scroll addSubview:header];
NSDictionary *headerdic = #{#"header":header};
NSArray *header_H = [NSLayoutConstraint constraintsWithVisualFormat:#"H:[header(150)]" options:0 metrics:Nil views:headerdic];
NSArray *header_V = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[header(30)]" options:0 metrics:Nil views:headerdic];
[header addConstraints:header_H];
[header addConstraints:header_V];
NSArray *header_posH = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-80-[header]" options:0 metrics:Nil views:headerdic];
NSArray *header_posV = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-20-[header]" options:0 metrics:Nil views:headerdic];
[self.view addConstraints:header_posH];
[self.view addConstraints:header_posV];
see the above image in 4s label is in center is correct , but in 6 it move to some left, what is the problem can any one help me.
To get it to work, it is also important how you want the layout to look on different devices. Should the label always have the width 150 and be centered or should it always have a 80 left and right padding?
This is what you have to decide but the constraints would look like this:
First case (same width and centered) :
NSLayoutConstraint *centerXConstraint = [NSLayoutConstraint constraintWithItem:header attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem: scroll attribute:NSLayoutAttributeCenterX multiplier:1.f constant:0];
[scroll addConstraint:centerXConstraint];
Second case (keep the padding between devices):
[scroll addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-leftPadding-[header]-leftPadding-|" options:0 metrics:#{ #"leftPadding": #(80) } views:#{ #"header": header }]];
Let me know hot it goes or if you need more help.
Try adding your constraint like this
NSLayoutConstraint *leadingConstraint= [NSLayoutConstraint constraintWithItem:self.contentView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_yourLabel attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0];
NSLayoutConstraint *topConstraint= [NSLayoutConstraint constraintWithItem:self.contentView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:_yourLabel attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
[self.contentView addConstraints:#[leadingConstraint,topConstraint]];
The issue seems to be that you have hard coded the values in. This is fine for iPhone 4 and 5 as they both have screens with width of 320. This means your hard coded value of 80 keeps it in the middle.
iPhone 6 though has a wider screen
iPhone 4 and 5
|---80---|--150--|---90---|
You can see this with a screenshot of your screenshot above:
Here I have added two green lines both the same length, although you think your label is in the middle on iPhone 4 and 5 it actually isn't.
What you need to do is one of two things:
Use NSLayoutConstraints to fix the view in the middle instead of setting values. There are various layout constraints that put it in the middle.
You can use centreX for this
NSLayoutConstraint * centreConstraint = [NSLayoutConstraint constraintWithItem:self.contentView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_yourLabel attribute:**NSLayoutAttributeCenterX** multiplier:1.0 constant:0];
Then add this constraint (NSLayoutConstraint can be quite fiddly so make sure you test it as this might not work)
This sets the label in the middle of the screen (hence the centreX - meaning centre of the x-axis)
The other way, not recommended if you are trying to have both landscape and portrait orientations, would be to calculate the width of the screen and so enter an accurate half way padding value.
You can calculate the width of the screen with
[UIScreen mainScreen].bounds.size.width
Then you can calculate the value you need it to be Eg in your case it would be
// Your label is 150 pixels wide so the space on each side is the width less 150 but half as you have the same amount on each side.
([UIScreen mainScreen].bounds.size.width - 150)/2
This is a way to simply calculate the value needed and is not as good as using NSLayoutConstraint. It is much simpler but much less versatile. I only include it for completeness.
My advice, look on some tutorials for NSLayoutConstraints this one is good and get better understanding of what you are constraining.

Dynamic trailing constraints with autolayout programmatically not working

I have set my constraints programmatically using visual format like this
[[self contentView] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[label1]-(==space1#1000)-[label2]-(>=space2#750)-|" options:0 metrics:metricsDictionary views:viewDictionary]];
Since I needed the leading space between [self contentView] and label1 to be dynamic I created a property and initialized it like this
[self setLeadingConstraint:[NSLayoutConstraint
constraintWithItem:[self label1]
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:[self contentView]
attribute:NSLayoutAttributeLeading
multiplier:1.0f constant:kLeadingSpace]];
I added all the constraints to [self contentView] and so far is working fine.
This is how it looks when label 1 is a long string
This is how it looks when label 1 is a short string
Now I need the trailing space to be dynamic as the leading space so I tried the same and initialized visual format constraints the variable trailing constraint as follows
[[self contentView] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[label1]-(==space1#1000)-[label2]" options:0 metrics:metricsDictionary views:viewDictionary]];
[self setTrailingSpaceConstraint:[NSLayoutConstraint
constraintWithItem:[self label2]
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:[self contentView]
attribute:NSLayoutAttributeTrailing
multiplier:1.0f
constant:kTrailingSpace]];
This time the trailing constraint is not set properly as images show

Creating constraint to center label in view

I am trying to programatically setup some constraints. I have one container view UIView which holds three subviews.
UIView - circleView
UILabel - label1
UILabel - label2
The circleview is shown at the top of the container at (0,0,width,80). The label1 is shown underneath the circleview with 5.0 padding.
I am now trying to add the label2 to be in the center of the circleView. How do I do this with AutoLayout programatically.
This is what I currently do.
NSDictionary *views = NSDictionaryOfVariableBindings(circleView,labelView, iconLbl);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[circleView(circleSize)]|" options:0 metrics:metrics views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[labelView]|" options:0 metrics:metrics views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[circleView(circleSize)]-(padding)-[labelView]-|" options:0 metrics:metrics views:views]];
The label2 is the iconLbl view in the dictionary.
This should be relatively straightforward - it helps to use xib to see how many constraints you actually need to get the effect you want. Constraining a label to be in the center of another view, both of which are in a parentView, only requires 2 constraints to be fully constrained. If this were a regular UIView, you'd need 4 constraints (x,y,width,height), but the label will automatically determine it's width and height from it's content, so it's not ambiguous. This is of course if you other views are all properly constrained, but you only asked about the label2 in the circle view.
I prefer to use the non-visual form for defining constraints because they read like mathematical equations. What you want is:
label2.centerX = circleView.centerX*1 + 0;
label2.centerY = circleView.centerY*1 + 0;
Since these are siblings with a common parent, the constraints are added to the parentView. So you get the following two constraints.
[parentView addConstraint:[NSLayoutConstraint constraintWithItem:label2 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:circleView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
[parentView addConstraint:[NSLayoutConstraint constraintWithItem:label2 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:circleView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
This is sufficient for getting label2 centered in the parentView. Any issues you get will likely be due to other constraints between your views not being properly specified.
Can you try this?
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[circleView(circleSize)]" options:0 metrics:metrics views:views]]; //Dont link to both the sides. Dock to the left edge
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self. labelView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0 ]]; //Specify the X
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self. labelView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0 ]]; //Specify Y
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[circleView(circleSize)]" options:0 metrics:metrics views:views]]; //Dock the circle to the top
With Masonry library
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(view);
}];

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

How do I represent the content of this UITableCell programmatically using Auto Layout?

I have just started using Auto Layout for my latest project and I was wondering what would be the most efficient way of laying out the following table cell:
Views A and B are both UILabels. C is a fixed size image and the view under A is an image that may or not be present. I am able to easily lay out A, B and C. But if the image under A is present, A's height needs to shrink proportionately and the image needs to fit underneath so that both are centered horizontally in the contentView.
I am trying to lay the entire cell out using code and the Visual Format language and have gotten quite close so far. The only problem is that the A and it's accompanying image aren't centered vertically in the container. You can see how far I have gotten in the image below:
And here is the code that I am using in my updateConstraints method. Note that with this code, I don't get an ambiguous layout:
NSDictionary *views = NSDictionaryOfVariableBindings(viewA, viewB, viewC);
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[viewA]-[viewB]-(>=8)-[viewC]-|"
options:0
metrics:nil
views:views]];
NSDictionary *metrics = #{#"width":#(40.0f), #"height":#(40.0f), #"priority":#(UILayoutPriorityRequired)};
[viewC addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[viewC(==width#priority)]"
options:0
metrics:metrics
views:#{#"viewC": _merchantLogo}]];
[viewC addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[viewC(==height#priority)]"
options:0
metrics:metrics
views:views]];
[viewA addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[viewA(>=75#750)]"
options:0
metrics:nil
views:views]];
[viewB addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[viewB(>=115#500)]"
options:0
metrics:nil
views:views]];
[self.contentView addConstraints:#[[NSLayoutConstraint constraintWithItem:viewC
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeCenterY multiplier:1.0f constant:0.0f],
[NSLayoutConstraint constraintWithItem:viewB
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeCenterY multiplier:1.0f constant:0.0f]]];
if (!viewD) {
[self.contentView addConstraints:#[[NSLayoutConstraint constraintWithItem:viewA
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeCenterY multiplier:1.0f constant:0.0f]]];
} else {
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[viewA][viewD]"
options:NSLayoutFormatAlignAllLeft
metrics:nil
views:NSDictionaryOfVariableBindings(viewA, viewD)]];
}
One of my ideas was to put A and the image below it in a container view and then lay them out within that view. But that seems kind of inefficient and I first want to make sure this isn't possible without using a container view.
So...
1.
Format
#"|-[_viewA(<=75)]-[viewB]-[viewC(==60)]-|"
Options
NSLayoutFormatAlignAllTop
2.
Format
#"V:|-[viewA]-[imageView(==10)]-|"
Options
NSLayoutFormatAlignCenterX | NSLayoutFormatAlignAllLeft
3.
Add individual constraints to constrain bottom of image view to bottom of viewB and viewC.
[NSLayotuConstraint constraintWithItem:viewB
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:imageView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0];
and other one...
This should give you what you want.

Resources