tableView cell dotted border not rendering properly - ios

Consider me novice in iOS programing. I am trying to show dotted border as the separator as well as a vertical dotted border between the fields, please check the screenshot for detailed view. Till now there is no problem, borders are rendering properly.But at the moment tableView get scrolled up or down, the position of vertical dotted lines are getting changed, please check screenshot. I know this is happening due to the cell reuse property. For all the cell dotted line creating method is same so, I am unable to find the culprit line of code. I am using ViewController and attached an UITableView to it, connected the datasource and delegates. Here is the code for dotted line creation.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
KKCustList *cell = [tableView dequeueReusableCellWithIdentifier:#"custList" forIndexPath:indexPath];
if (cell==nil)
{
cell = [[KKCustList alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"custList"];
}
/** Creating dotted lines for border **/
CAShapeLayer *shapelayer = [CAShapeLayer layer];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, cell.frame.size.height)];
[path addLineToPoint:CGPointMake(cell.frame.size.width, cell.frame.size.height)];
shapelayer.strokeStart = 0.0;
shapelayer.strokeColor = [UIColor firstRowColor].CGColor;
shapelayer.lineWidth = 2.0;
shapelayer.lineJoin = kCALineJoinRound;
shapelayer.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:1],[NSNumber numberWithInt:2 ], nil];
shapelayer.path = path.CGPath;
[cell.layer addSublayer:shapelayer];
/** creating vertical dotted line **/
CAShapeLayer *shapelayer1 = [CAShapeLayer layer];
UIBezierPath *path1 = [UIBezierPath bezierPath];
[path1 moveToPoint:CGPointMake(cell.soLabel.bounds.origin.x + cell.soLabel.bounds.size.width, cell.soLabel.bounds.origin.y + 10)];
[path1 addLineToPoint:CGPointMake(cell.soLabel.bounds.origin.x + cell.soLabel.bounds.size.width, cell.soLabel.bounds.size.height)];
shapelayer1.strokeStart = 0.0;
shapelayer1.strokeColor = [UIColor firstRowColor].CGColor;
shapelayer1.lineWidth = 1.0;
shapelayer1.lineJoin = kCALineJoinRound;
shapelayer1.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:1],[NSNumber numberWithInt:2 ], nil];
shapelayer1.path = path1.CGPath;
[cell.layer addSublayer:shapelayer1];
UPDATE
moved the code to awakeFromNib
#implementation KKCustList
#synthesize shapelayer1;
- (void)awakeFromNib {
[super awakeFromNib];
/** creating vertical dotted line **/
CAShapeLayer *shapelayer1 = [CAShapeLayer layer];
UIBezierPath *path1 = [UIBezierPath bezierPath];
[path1 moveToPoint:CGPointMake(self.soLabel.bounds.origin.x + self.soLabel.bounds.size.width, self.soLabel.bounds.origin.y + 10)];
[path1 addLineToPoint:CGPointMake(self.soLabel.bounds.origin.x + self.soLabel.bounds.size.width, self.soLabel.bounds.size.height)];
shapelayer1.strokeStart = 0.0;
shapelayer1.strokeColor = [UIColor firstRowColor].CGColor;
shapelayer1.lineWidth = 1.0;
shapelayer1.lineJoin = kCALineJoinRound;
shapelayer1.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:1],[NSNumber numberWithInt:2 ], nil];
shapelayer1.path = path1.CGPath;
[self.contentView.layer addSublayer:shapelayer1];
}

Your current code has a couple of issues:
You keep adding new layers, you never remove old ones, so each time a cell is reused it'll take more memory and the lines could be in the wrong place
You never move the sublayers when the cell size changes
So you need a couple of different fixes:
Add the lines in your cell in awakeFromNib, so they're just added once in the same way that all your other views are
Override layoutSubviews in your cell and move the layers you've added each time it's called so they don't overlap other views
I'd actually drop the sublayers approach probably and use an image view for the line. If the lines might change quite a lot then I'd likely use a custom view class which has a sublayer (as per your current code) and the resizing / layout logic as described above. The reason for that is so that you can apply auto-layout constraints to the view and it will move in relation to the cell and your other views automatically.
You should also remove
if (cell==nil)
{
cell = [[KKCustList alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"custList"];
}
because it will never be used, a cell will always be created for you.

Related

Drawing vertical line in tableview cell between Dynamic Labels with AutoLayout

I have a table view containing two labels which supports multiline and placed vertically in the container. I have fixed the distance between the both label. Texts of Label can be of two or in three lines. I am drawing a line between both the labels using CAShapeLayer and UIBezierPath. My Problem is, I am not getting the actual width of the label, So the vertical line doesn't draw at the desired place. Here is the code I am using for the drawing. What I can think of is my lines are getting drawn first and the text of labels are getting set after that can be the issue. How can I fixed that?
Constraints
leading space from superview
trailing from second label
vertically in container
TableViewCell.m
#implementation KKCDDCell
{
CGFloat borderWidth;
UIBezierPath *borderPath;
}
- (void) awakeFromNib
{
[super awakeFromNib];
/** creating vertical line**/
[self verticalLine];
[self dottedLineWithPath:borderPath withborderWidth:borderWidth];
}
- (void)verticalLine
{
borderPath = [UIBezierPath bezierPath];
[borderPath moveToPoint:CGPointMake(self.labelOne.frame.origin.x +self.labelOne.frame.size.width + 10, self.frame.origin.y + 2)];
[borderPath addLineToPoint:CGPointMake(self.labelOne.frame.origin.x +self.labelOne.frame.size.width + 10, self.frame.origin.y + self.frame.size.height - 2)];
borderWidth = 1.0;
}
- (void)dottedLineWithPath:(UIBezierPath *)path withborderWidth:(CGFloat)lineWidth
{
CAShapeLayer *shapelayer = [CAShapeLayer layer];
shapelayer.strokeStart = 0.0;
shapelayer.strokeColor = [UIColor firstRowColor].CGColor;
shapelayer.lineWidth = borderWidth;
shapelayer.lineJoin = kCALineJoinRound;
shapelayer.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:1],[NSNumber numberWithInt:2 ], nil];
shapelayer.path = path.CGPath;
[self.contentView.layer addSublayer:shapelayer];
}

iOS Clear UIView and redraw drawRect:

I want my custom view to clear everything (subviews, sublayers, drawings, etc.) in it and call drawRect: again when pressing a button, so it will be like getting a clean slate and painting on it again.
Right now, I've tried using setNeedsDisplay and it just draws over the whole thing without cleaning the previous work.
A snippet of code in my drawRect:
if (_displayYAxisAverageLine) {
// Check if y axis data are numbers
for (id yValue in yAxisValues) {
if (![yValue isKindOfClass:[NSNumber class]]) {
return;
}
}
NSNumber *average = [yAxisValues valueForKeyPath:#"#avg.self"];
CGFloat scale = [self scaleForYValue:average];
CGFloat avgLineY = roundf(graphHeight * scale)+_graphInset;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, avgLineY)];
[path addLineToPoint:CGPointMake(contentWidth, avgLineY)];
CAShapeLayer *avgLine = [CAShapeLayer layer];
avgLine.path = path.CGPath;
avgLine.fillColor = [UIColor clearColor].CGColor;
avgLine.strokeColor = _borderColor.CGColor;
avgLine.lineWidth = 1.f;
avgLine.lineDashPattern = #[#2, #2];
[self.contentView.layer addSublayer:avgLine];
}

Removing a custom sublayer from a custom uitableviewcell

I need rounded, variable-height tableview cells.
I use the following code to create the rounded background:
- (void)roundView:(UIView *)view withRadius:(float)radius andColour:(UIColor *)colour
{
view.backgroundColor = [UIColor whiteColor];
CAShapeLayer *layer = [[CAShapeLayer alloc] init];
CGMutablePathRef pathRef = CGPathCreateMutable();
CGRect bounds = CGRectInset(view.bounds, 0.0f, 0.0f);
CGPathAddRoundedRect(pathRef, nil, bounds, radius, radius);
layer.path = pathRef;
CFRelease(pathRef);
layer.fillColor = colour.CGColor;
[view.layer insertSublayer:layer atIndex:0];
}
The problem is that if a tall cell is rounded, when it is reused, the earlier sublayer is still there and although the new (lesser) height is correct, the appearance is that the bottom edge of the box is not rounded. Presumably, the (larger) pre-existing layer is being clipped.
An obvious thought was to remove the sublayer, but I can't a way of doing it. I've tried creating a new cell, without reusing one, but this doesn't seem possible.
Create a custom layer property in your cell class, assign your layer to it and call -removeFromSuperLayer on it in cell's -prepareForReuse.
I recalled somewhere that I'd done this before to a button...
self.buttonCancel.layer.cornerRadius = 10;
self.buttonCancel.clipsToBounds = YES;
and it works here too...
cell.textBlurb.layer.cornerRadius = 10;
cell.textBlurb.clipsToBounds = YES;

How to handle UIView rounded corners (using CAShapeLayer) on rotation

I have a UITableViewCell containing a UIView. I would like to round all the corners of the UIView when the cell is the only row in the section (i.e. not selected in my implementation). If the cell is selected then I would only like to round the top two corners of the UIView. I am attempting to do this in the custom tableviewcell's layoutSubviews.
- (void)layoutSubviews
{
[super layoutSubviews];
self.roundedView.layer.mask = nil;
if (self.sectionSelected) {
self.maskLayer = [CAShapeLayer layer];
self.maskLayer.path = [UIBezierPath bezierPathWithRoundedRect:self.roundedView.bounds byRoundingCorners:(UIRectCornerTopLeft|UIRectCornerTopRight) cornerRadii:CGSizeMake(10, 10)].CGPath;
self.roundedView.layer.mask = self.maskLayer;
} else {
self.maskLayer = [CAShapeLayer layer];
self.maskLayer.path = [UIBezierPath bezierPathWithRoundedRect:self.roundedView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(10, 10)].CGPath;
self.roundedView.layer.mask = self.maskLayer;
}
}
Unfortunately this method does not work all the time. If I have a the cell selected in portrait (self.sectionSelected = YES). Then deselect (self.sectionSelected = NO). Then rotate to landscape and select the cell again. It displays the cell the size it is suppose to be in portrait. So I believe it is still applying the portrait layer mask and not the landscape one. If I scroll it out of view and then back it in, it updates to the appropriate width. Also of note, I call [self.tableView reloadData]; in didRotateFromInterfaceOrientation.
Any ideas for avoiding this issue? Thanks in advance
Resolved by simply adding "self.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth;"

UITableViewCell with CAShapeLayer bug with appearing

I have a table view cell with custom backgroundView. This background view have an overriden method:
+ (Class)layerClass
{
return [CAShapeLayer class];
}
In code I specify the path, stroke color, fill color and line width for layer of background view of my cells. Next I try to insert section into table view using method:
- (void)insertSections:(NSIndexSet *)sections
withRowAnimation:(UITableViewRowAnimation)animation;
And at this point I get a bug: layers of inserted cells are displayed only after animation of inserting. That is, during the animation cells have no background and border.
UPDATE:
This is how I set custom background for my cells:
UITableViewCell* cell = /* initializing cell */
CGRect frame = cell.frame;
frame.size.width = tableView.frame.size.width;
frame.origin = CGPointZero;
cell.backgroundView = [[MYCellBackgroundView alloc] initWithFrame:frame];
CAShapeLayer* shapeLayer = (CAShapeLayer *)cell.backgroundView.layer;
CGMutablePathRef path = CGPathCreateMutable();
/* building path with some arcs and lines */
shapeLayer.path = path;
CGPathRelease(path);
shapeLayer.fillColor = FILL_COLOR.CGColor;
shapeLayer.strokeColor = BORDER_COLOR.CGColor;
shapeLayer.lineWidth = LINE_WIDTH;
And I forgot to specify that I use CAShapeLayer only for customizing view of cells, not for animation.

Resources