Drawing vertical line in tableview cell between Dynamic Labels with AutoLayout - ios

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

Related

Drop `points` along a UIBezierPath such that `center` of points lies ON the path

The problem statement:
Create a semi-circular dial that has dots along it's circumference. User can pan and rotate this dial.
Approach:
Created a UIView
Added a circle with radius that's double the height of the UIView (using [UIBezierPath bezierPathWithOvalInRect] and CAShapeLayer)
Add dots (circular CALayer objects) along the circumference of this circle, such that the center of these dots lies ON the circumference.
The obstacle:
The dots are plotted along a path, but the center of these dots does not lie on the circumference.
The code is as follows:
#property (nonatomic, retain) CAShapeLayer* dialLineLayer;
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
self.dialLineLayer = [CAShapeLayer layer];
CGRect path = CGRectMake(SCREEN_WIDTH /2 - self.radius, -self.radius, 2 * self.radius, 2 * self.radius);
[self.dialLineLayer setPath:[[UIBezierPath bezierPathWithOvalInRect:path] CGPath]];
[self.dialLineLayer setStrokeColor:[[UIColor la_white10Color] CGColor]];
[self.dialLineLayer setFillColor:[[UIColor clearColor] CGColor]];
[self.dialLineLayer setLineWidth:3.0f];
[self.layer addSublayer:self.dialLineLayer];
[self addDotsOnLineLayer];
}
return self;
}
- (CGFloat) radius {
// View has an aspect ratio constraint of 75:34 (W:H)
return SCREEN_WIDTH * 34 / 75 - 10; // Circle radius is 10px less that View's height
}
- (CGFloat) circumference {
return M_2_PI * self.radius; // 2Pi * radius
}
- (void) addDotsOnLineLayer {
CGPoint dotPoint = CGPointMake(SCREEN_WIDTH /2 - self.radius - self.radius/2, 0);
for (double t = 0; t < 2*M_PI; t += 0.2) {
CGFloat dotRadius = arc4random_uniform(10) - arc4random_uniform(3); // Random radius between 3px and 10px
CALayer* dotLayer = [CALayer layer];
dotLayer.frame = CGRectMake(dotPoint.x, dotPoint.y, dotRadius, dotRadius);
dotLayer.cornerRadius = dotRadius / 2;
dotLayer.masksToBounds = YES;
dotLayer.backgroundColor = [UIColor greenColor].CGColor;
dotPoint.x = self.radius * cos(t) + (SCREEN_WIDTH / 2);
dotPoint.y = self.radius * sin(t) + 0;
[self.dialLineLayer addSublayer:dotLayer];
}
}
Try this: (Brace for major 'doh' + facepalm ;) )
dotLayer.frame = CGRectMake(0, 0, dotRadius, dotRadius);
dotLayer.position = dotPoint;
Explanation
You are currently positioning the origin of your layer on the path. (Remove the corner radius and you'll see what I mean.) However, since you want the center of the layer on the path, you have to assign to .position.
Note that this is only true, as long as you leave the .anchorPoint property of the layer at {0.5, 0.5}.
See Core Animation Basics for more info.

Circular UIButtons Getting Distorted on Rotation Animation Due to Resize

I have this 10 keypad on iPhone that has basically the same screen as the iPhone unlock screen, only mine rotates which resizes the buttons to fit the screen. The problem is that the animation of rotating the device distorts the round shape because they have changed size, but the cornerRadius is the same until it completes the animation. Once the rotation animation has completed, the buttons get the radius set again, thus making them round. I don't know how to have the UIButtons always round, specifically during the rotation animation. Here's basically what I have:
- (void)viewDidLayoutSubviews {
[self setupButtons];
}
- (void)setupButtons {
for (UIButton *button in self.touchPadButtons) {
[self roundifyButton:button withRadius:radius];
}
}
- (void)roundifyButton:(UIButton *)button {
NSInteger height = button.frame.size.height;
radius = height/2;
button.layer.cornerRadius = radius;
button.layer.borderWidth = .6f;
button.layer.borderColor = [UIColor whiteColor].CGColor;
button.clipsToBounds = YES;
}
I've tried using:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
but it seems like in order for setting the radius to work from that method, I'd have to set the size of my buttons programmatically instead of using autolayout. Does anyone have any magical suggestions on handling this? I would sure love to not rotate, like Apple, but unfortunately that decision was not mine.
Wow, this was tougher than I thought it would be. Fortunately, WWDC is going on right now and I was able to get a solution from the Interface Builder lab. His solution was to subclass UIButton and overwrite the drawRect: method. So this is the only method you should have in the CircleButton class. One issue I found is that the lineWidth property doesn't get set before it's initialized by the nib. I overwrote the init method and set a default value, but it doesn't get hit the first time when the nib initializes the buttons. So I had to add the default value in the drawRect: method. I hope this helps people who need circular UIButtons that can resize.
- (void)drawRect:(CGRect)rect {
[[UIColor whiteColor] set];
if (!self.lineWidth) {
self.lineWidth = 0.75;
}
CGRect bounds = [self bounds];
CGRect circleRect = CGRectMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds), 0, 0);
CGFloat radius = MIN(bounds.size.width, bounds.size.height) / 2.0;
circleRect = CGRectInset(circleRect, -radius, -radius);
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(circleRect, self.lineWidth / 2.0, self.lineWidth / 2.0)];
[path setLineWidth:self.lineWidth];
[path stroke];
}
In case you want to animate the button click the way iPhone lock-screen does, you'll need to add this to the CircleButton class. Otherwise only the titleLabel will be highlighted.
- (void)drawRect:(CGRect)rect {
[[UIColor whiteColor] set];
if (!self.lineWidth) {
self.lineWidth = 0.75;
}
CGRect bounds = [self bounds];
CGRect circleRect = CGRectMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds), 0, 0);
CGFloat radius = MIN(bounds.size.width, bounds.size.height) / 2.0;
circleRect = CGRectInset(circleRect, -radius, -radius);
self.layer.cornerRadius = radius;
self.clipsToBounds = YES;
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(circleRect, self.lineWidth / 2.0, self.lineWidth / 2.0)];
[path setLineWidth:self.lineWidth];
[path stroke];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// highlight button on click
self.backgroundColor = [UIColor lightGrayColor];
[super touchesBegan:touches withEvent:event];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
// remove highlight
self.backgroundColor = [UIColor clearColor];
}

tableView cell dotted border not rendering properly

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.

Animate a CAShapeLayer to draw a progress circle

I am trying to draw a circle that will be used to indicate progress. The progress updates might come in quickly, and I want to animate the changes to the circle.
I have tried doing this with the below methods, but nothing seems to work. Is this possible?
- (id)initWithFrame:(CGRect)frame strokeWidth:(CGFloat)strokeWidth insets:(UIEdgeInsets)insets
{
if (self = [super initWithFrame:frame])
{
self.strokeWidth = strokeWidth;
CGPoint arcCenter = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGFloat radius = CGRectGetMidX(self.bounds) - insets.top - insets.bottom;
self.circlePath = [UIBezierPath bezierPathWithArcCenter:arcCenter
radius:radius
startAngle:M_PI
endAngle:-M_PI
clockwise:YES];
[self addLayer];
}
return self;
}
- (void)setProgress:(double)progress {
_progress = progress;
[self updateAnimations];
}
- (void)addLayer {
CAShapeLayer *progressLayer = [CAShapeLayer layer];
progressLayer.path = self.circlePath.CGPath;
progressLayer.strokeColor = [[UIColor whiteColor] CGColor];
progressLayer.fillColor = [[UIColor clearColor] CGColor];
progressLayer.lineWidth = self.strokeWidth;
progressLayer.strokeStart = 10.0f;
progressLayer.strokeEnd = 40.0f;
[self.layer addSublayer:progressLayer];
self.currentProgressLayer = progressLayer;
}
- (void)updateAnimations{
self.currentProgressLayer.strokeEnd = self.progress;
[self.currentProgressLayer didChangeValueForKey:#"endValue"];
}
Currently this code doesn't even draw the circle, but removing the progressLayer's strokeStart and strokeEnd will draw the full circle.
How can I get it to begin at a certain point and start drawing the circle based on me setting my progress property?
I figured it out. The strokeStart and strokeEnd values are from 0.0 to 1.0, so the values I was giving it were way too high.
So, I just updated my setting to:
- (void)setProgress:(double)progress {
_progress = progress * 0.00460;
[self updateAnimations];
}
My answer with example here. In general you should add the animation to CAShapeLayer.

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