Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Draw a single point line in iOS

I was wondering what is the best way to draw a single point line? My goal is to draw this line in a tableViewCell to make it look just like the native cell separator. I don't want to use the native separator because i want to make in a different color and in a different position (not the bottom..).

At first i was using a 1px UIView and colored it in grey. But in Retina displays it looks like 2px. Also tried using this method:

- (void)drawLine:(CGPoint)startPoint endPoint:(CGPoint)endPoint inColor:(UIColor *)color {

    CGMutablePathRef straightLinePath = CGPathCreateMutable();
    CGPathMoveToPoint(straightLinePath, NULL, startPoint.x, startPoint.y);
    CGPathAddLineToPoint(straightLinePath, NULL, endPoint.x, endPoint.y);

    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = straightLinePath;
    UIColor *fillColor = color;
    shapeLayer.fillColor = fillColor.CGColor;
    UIColor *strokeColor = color;
    shapeLayer.strokeColor = strokeColor.CGColor;
    shapeLayer.lineWidth = 0.5f;
    shapeLayer.fillRule = kCAFillRuleNonZero;
    [self.layer addSublayer:shapeLayer];
}

It works in like 60% of the times for some reason.. Is something wrong with it? Anyway ,i'd be happy to hear about a better way.

Thanks.

like image 795
Lirik Avatar asked Mar 27 '14 16:03

Lirik


5 Answers

I did the same with a UIView category. Here are my methods :

#define SEPARATOR_HEIGHT 0.5

- (void)addSeparatorLinesWithColor:(UIColor *)color
{
    [self addSeparatorLinesWithColor:color edgeInset:UIEdgeInsetsZero];
}


- (void)addSeparatorLinesWithColor:(UIColor *)color edgeInset:(UIEdgeInsets)edgeInset
{

    UIView *topSeparatorView = [[UIView alloc] initWithFrame:CGRectMake(edgeInset.left, - SEPARATOR_HEIGHT, self.frame.size.width - edgeInset.left - edgeInset.right, SEPARATOR_HEIGHT)];
    [topSeparatorView setBackgroundColor:color];
    [self addSubview:topSeparatorView];

    UIView *separatorView = [[UIView alloc] initWithFrame:CGRectMake(edgeInset.left, self.frame.size.height + SEPARATOR_HEIGHT, self.frame.size.width - edgeInset.left - edgeInset.right, SEPARATOR_HEIGHT)];
    [separatorView setBackgroundColor:color];
    [self addSubview:separatorView];
}

Just to add to Rémy's great answer, it's perhaps even simpler to do this. Make a class UILine.m

@interface UILine:UIView
@end
@implementation UILine
-(id)awakeFromNib
    {
    // careful, contentScaleFactor does NOT WORK in storyboard during initWithCoder.
    // example, float sortaPixel = 1.0/self.contentScaleFactor ... does not work.
    // instead, use mainScreen scale which works perfectly:
    float sortaPixel = 1.0/[UIScreen mainScreen].scale;
    UIView *topSeparatorView = [[UIView alloc] initWithFrame:
        CGRectMake(0, 0, self.frame.size.width, sortaPixel)];

    topSeparatorView.userInteractionEnabled = NO;
    [topSeparatorView setBackgroundColor:self.backgroundColor];
    [self addSubview:topSeparatorView];

    self.backgroundColor = [UIColor clearColor];
    self.userInteractionEnabled = NO;
    }
@end

In IB, drop in a UIView, click identity inspector and rename the class to a UILine. Set the width you want in IB. Set the height to 1 or 2 pixels - simply so you can see it in IB. Set the background colour you want in IB. When you run the app it will become a 1-pixel line, that width, in that colour. (You probably should not be affected by any default autoresize settings in storyboard/xib, I couldn't make it break.) You're done.

Note: you may think "Why not just resize the UIView in code in awakeFromNib?" Resizing views upon loading, in a storyboard app, is problematic - see the many questions here about it!

Interesting gotchya: it's likely you'll just make the UIView, say, 10 or 20 pixels high on the storyboard, simply so you can see it. Of course it disappears in the app and you get the pretty one pixel line. But! be sure to remember self.userInteractionEnabled = NO, or it might get over your other, say, buttons!


2016 solution ! https://stackoverflow.com/a/34766567/294884

like image 112
Rémy Virin Avatar answered Nov 18 '22 04:11

Rémy Virin


shapeLayer.lineWidth = 0.5f;

That's a common mistake and is the reason this is working only some of the time. Sometimes this will overlap pixels on the screen exactly and sometimes it won't. The way to draw a single-point line that always works is to draw a one-point-thick rectangle on integer boundaries, and fill it. That way, it will always match the pixels on the screen exactly.

To convert from points to pixels, if you want to do that, use the view's scale factor.

Thus, this will always be one pixel wide:

CGContextFillRect(con, CGRectMake(0,0,desiredLength,1.0/self.contentScaleFactor));

Here's a screen shot showing the line used as a separator, drawn at the top of each cell:

enter image description here

The table view itself has no separators (as is shown by the white space below the three existing cells). I may not be drawing the line in the position, length, and color that you want, but that's your concern, not mine.

like image 2
matt Avatar answered Nov 18 '22 06:11

matt


AutoLayout method:

I use a plain old UIView and set its height constraint to 1 in Interface Builder. Attached it to the bottom via constraints. Interface builder doesn't allow you to set the height constraint to 0.5, but you can do it in code.

Make a connector for the height constraint, then call this:

// Note: This will be 0.5 on retina screens
self.dividerViewHeightConstraint.constant =  1.0/[UIScreen mainScreen].scale 

Worked for me.

FWIW I don't think we need to support non-retina screens anymore. However, I am still using the main screen scale to future proof the app.

like image 2
n13 Avatar answered Nov 18 '22 06:11

n13


You have to take into account the scaling due to retina and that you are not referring to on screen pixels. See Core Graphics Points vs. Pixels.

like image 1
Volker Avatar answered Nov 18 '22 06:11

Volker


Addition to Rémy Virin's answer, using Swift 3.0 Creating LineSeparator class:

import UIKit

class LineSeparator: UIView {

override func awakeFromNib() {

    let sortaPixel: CGFloat = 1.0/UIScreen.main.scale

    let topSeparatorView = UIView()
    topSeparatorView.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: sortaPixel)

    topSeparatorView.isUserInteractionEnabled = false
    topSeparatorView.backgroundColor = self.backgroundColor

    self.addSubview(topSeparatorView)
    self.backgroundColor = UIColor.clear

    self.isUserInteractionEnabled = false   
    }
}
like image 1
Mr_Vlasov Avatar answered Nov 18 '22 05:11

Mr_Vlasov