带虚线的UIView

36

我的现状:

enter image description here

为了创建这条线,我基本上有一个UIView并执行以下操作:

void setLayerToLineFromAToB(CALayer *layer, CGPoint a, CGPoint b, CGFloat lineWidth)
{
    CGPoint center = { 0.5 * (a.x + b.x), 0.5 * (a.y + b.y) };
    CGFloat length = sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
    CGFloat angle = atan2(a.y - b.y, a.x - b.x);

    layer.position = center;
    layer.bounds = (CGRect) { {0, 0}, { length + lineWidth, lineWidth } };
    layer.transform = CATransform3DMakeRotation(angle, 0, 0, 1);
}

注意:这段代码是从stackoverflow获取的,如果有人能提供参考资料,我将不胜感激。

我的需求:

enter image description here

好的,我想做的“唯一”事情就是在UIView上创建这个图案。我知道可以使用Quartz2D来实现它(可以在这里找到一个简单的方法)。但是我想通过操作CALayer而不是去调用绘制方法来实现它。为什么?因为我对我的UIView进行了变换,无法正确地使用draw方法进行绘制。

编辑1:

为了说明我的问题:

enter image description here

通常你会有一个UIView,然后你只需要在里面绘制一些东西(在这种情况下是一条简单的线)。我发现的解决方案是,不是绘制任何东西,而是直接变换UIView本身,这样就可以摆脱“灰色”区域。如果你想要一条完全填充的线,那么这个方法是有效的,但当你想要一个虚线时,问题就来了。


为什么不使用Quartz2D在图层上绘制,然后对图层进行变换呢? - lukaswelte
你的意思是基本上在draw方法中绘制它,然后应用变换吗? - Rui Peres
是的,那就是我的意思... 就像你绘制一张“图像”,然后只需转换这个“图像”。 - lukaswelte
@lukaswelte,请检查我的编辑,看看你所说的是否有意义。 - Rui Peres
去掉灰色背景?为什么不在drawRect中画线,然后转换您的UIView将会做出您想要的完美效果...并且这适用于您之前在视图上绘制的任何形状。 - lukaswelte
9个回答

42

检查UIBezierPath setLineDash:count:phase:方法:链接

- (void)setLineDash:(const CGFloat *)pattern count:(NSInteger)count phase:(CGFloat)phase` method. 

这允许您绘制虚线。

  1. 首先添加一个CAShapeLayer。将其作为子层添加到您的UIView中。它有一个path属性。
  2. 现在创建一个UIBezierPath对象。使用setLineDash绘制该线条。

例如:

 UIBezierPath *path = [UIBezierPath bezierPath];
 //draw a line
 [path moveToPoint:yourStartPoint]; //add yourStartPoint here
 [path addLineToPoint:yourEndPoint];// add yourEndPoint here
 [path stroke];

 CGFloat dashPattern[] = {2.0f,6.0f,4.0f,2.0f}; //make your pattern here
 [path setLineDash:dashPattern count:4 phase:3];

 UIColor *fill = [UIColor blueColor];
 shapelayer.strokeStart = 0.0;
 shapelayer.strokeColor = fill.CGColor;
 shapelayer.lineWidth = 5.0;
 shapelayer.lineJoin = kCALineJoinMiter;
 shapelayer.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:10],[NSNumber numberWithInt:7], nil];
 shapelayer.lineDashPhase = 3.0f;
 shapelayer.path = path.CGPath;

注意: 此答案提供了一个提示,以便您可以根据您的要求进行相应的改进。


你是在应用转换之前还是之后进行这个操作? - Rui Peres
我没有应用变换。Jack Boy,你创建了像线一样的图层。但实际上你没有画线。 - Paresh Navadiya
我正在UIViewinitWithFrame:方法中进行操作。我收到了这种错误:<Error>: CGContextSaveGState: invalid context 0x0 - Rui Peres
float dashPattern[] = {2,6,4,2} should be CGFloat dashPattern[] = {2,6,4,2} - Stewart Macdonald

32

Swift4 • Xcode 9中绘制虚线

创建一个CAShapeLayer并使用lineDashPattern

extension UIView {

    func addDashedBorder() {
        //Create a CAShapeLayer
        let shapeLayer = CAShapeLayer()
        shapeLayer.strokeColor = UIColor.red.cgColor
        shapeLayer.lineWidth = 2
        // passing an array with the values [2,3] sets a dash pattern that alternates between a 2-user-space-unit-long painted segment and a 3-user-space-unit-long unpainted segment
        shapeLayer.lineDashPattern = [2,3]

        let path = CGMutablePath()
        path.addLines(between: [CGPoint(x: 0, y: 0),
                                CGPoint(x: self.frame.width, y: 0)])
        shapeLayer.path = path
        layer.addSublayer(shapeLayer)
    }
}

用法:

dashView.addDashedBorder()

输出:

enter image description here


如何使线条具有完整的宽度? - Zin Win Htet
为什么不只使用 UIView - Jack

24

注意:Prince的代码确实帮了我很多,所以我会给他加10分作为感谢。但最终,我还是要自己写出代码。我也会添加一些背景信息,以便于未来的读者可以更好地理解。


最终的代码如下:

-(void)updateLine{

      // Important, otherwise we will be adding multiple sub layers
      if ([[[self layer] sublayers] objectAtIndex:0])
        {
            self.layer.sublayers = nil;
        }

        CAShapeLayer *shapeLayer = [CAShapeLayer layer];
        [shapeLayer setBounds:self.bounds];
        [shapeLayer setPosition:self.center];
        [shapeLayer setFillColor:[[UIColor clearColor] CGColor]];
        [shapeLayer setStrokeColor:[[UIColor blackColor] CGColor]];
        [shapeLayer setLineWidth:3.0f];
        [shapeLayer setLineJoin:kCALineJoinRound];
        [shapeLayer setLineDashPattern:
        [NSArray arrayWithObjects:[NSNumber numberWithInt:10],
        [NSNumber numberWithInt:5],nil]];

        // Setup the path
        CGMutablePathRef path = CGPathCreateMutable();
        CGPathMoveToPoint(path, NULL, beginPoint.center.x, beginPoint.center.y);
        CGPathAddLineToPoint(path, NULL, endPoint.center.x, endPoint.center.y);

        [shapeLayer setPath:path];
        CGPathRelease(path);

        [[self layer] addSublayer:shapeLayer];
}
在我的情况下,beginPoint和endPoint是可由用户通过使用KVO移动的。所以当它们中的任意一个移动时:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqual:@"position"])
    {
        [self updateLine];
    }
}

我试着研究了Prince的代码。我在draw:方法上进行了尝试,在虚线之间添加了一条细线(有点奇怪...),还尝试了initWithFrame:。但是,如果没有任何修改,他的代码会在控制台上给我这种错误:

<Error>: CGContextSaveGState: invalid context 0x0
<Error>: CGContextSetLineWidth: invalid context 0x0
<Error>: CGContextSetLineJoin: invalid context 0x0
<Error>: CGContextSetLineCap: invalid context 0x0
<Error>: CGContextSetMiterLimit: invalid context 0x0
<Error>: CGContextSetFlatness: invalid context 0x0
<Error>: CGContextAddPath: invalid context 0x0
<Error>: CGContextDrawPath: invalid context 0x0
<Error>: CGContextRestoreGState: invalid context 0x0

15

Swift 2.2

将此放在这里以节省其他人的时间。

extension UIView {
    func addDashedLine(color: UIColor = UIColor.lightGrayColor()) {
        layer.sublayers?.filter({ $0.name == "DashedTopLine" }).map({ $0.removeFromSuperlayer() })
        self.backgroundColor = UIColor.clearColor()
        let cgColor = color.CGColor

        let shapeLayer: CAShapeLayer = CAShapeLayer()
        let frameSize = self.frame.size
        let shapeRect = CGRect(x: 0, y: 0, width: frameSize.width, height: frameSize.height)

        shapeLayer.name = "DashedTopLine"
        shapeLayer.bounds = shapeRect
        shapeLayer.position = CGPoint(x: frameSize.width / 2, y: frameSize.height / 2)
        shapeLayer.fillColor = UIColor.clearColor().CGColor
        shapeLayer.strokeColor = cgColor
        shapeLayer.lineWidth = 1
        shapeLayer.lineJoin = kCALineJoinRound
        shapeLayer.lineDashPattern = [4, 4]

        let path: CGMutablePathRef = CGPathCreateMutable()
        CGPathMoveToPoint(path, nil, 0, 0)
        CGPathAddLineToPoint(path, nil, self.frame.width, 0)
        shapeLayer.path = path

        self.layer.addSublayer(shapeLayer)
    }
}

2
只是一个提示:你可以使用.forEach()而不是map,这样编译器就不会警告map的未使用结果。 - Bence Pattogato

15

这是Alexandre G的回答的Swift 3版本 https://dev59.com/mWct5IYBdhLWcg3wcs-G#38194152

extension UIView {

        func addDashedLine(color: UIColor = .lightGray) {
            layer.sublayers?.filter({ $0.name == "DashedTopLine" }).map({ $0.removeFromSuperlayer() })
            backgroundColor = .clear

            let shapeLayer = CAShapeLayer()
            shapeLayer.name = "DashedTopLine"
            shapeLayer.bounds = bounds
            shapeLayer.position = CGPoint(x: frame.width / 2, y: frame.height / 2)
            shapeLayer.fillColor = UIColor.clear.cgColor
            shapeLayer.strokeColor = color.cgColor
            shapeLayer.lineWidth = 1
            shapeLayer.lineJoin = kCALineJoinRound
            shapeLayer.lineDashPattern = [4, 4]

            let path = CGMutablePath()
            path.move(to: CGPoint.zero)
            path.addLine(to: CGPoint(x: frame.width, y: 0))
            shapeLayer.path = path

            layer.addSublayer(shapeLayer)
        }
}

8

被接受的答案存在坐标问题。线条将绘制在下方一定距离处。我无法弄清楚为什么以及Y坐标增加了多少距离。

有一种方法可以绘制带有正确坐标的虚线:

-(void)drawRect:(CGRect)rect
{
     CGContextBeginPath(cx);
     CGContextRef cx = UIGraphicsGetCurrentContext();
     CGContextSetLineWidth(cx, _thickness);
     CGContextSetStrokeColorWithColor(cx, _color.CGColor);

     CGFloat dash[] = {_dashedLength,_dashedGap};
     CGContextSetLineDash(cx, 0, dash, 2); // nb "2" == ra count
//    CGContextSetLineCap(cx, kCGLineCapRound);

     CGContextMoveToPoint(cx, 0, _thickness);
     CGContextAddLineToPoint(cx, self.bounds.size.width, _thickness);
     CGContextStrokePath(cx);
     CGContextClosePath(cx);
}

这个答案来自2017年使用IBDesignable绘制虚线(不是破折号!)

当你想要一条黑色的虚线时,不要忘记将背景颜色设置为白色!!默认情况下,视图具有黑色背景颜色,线条颜色也是黑色,因此我以为它是实线。这让我花费了半天时间才发现。T_T


1
这可能对你来说有点晚了,但你可以查看我在@Shaheen Ghiassy答案下的评论。 - Bryan P
花了我半个小时T_T,幸亏有你的警告,否则可能会更久。 - fogisland

6

更新Swift 5UIBezierPath

对于那些使用UIBezierPath而不是CAShapeLayer的人,以下是实现方法

class MyView: UIView {

    override func draw(_ rect: CGRect) {

        let path = UIBezierPath()
        // >> define the pattern & apply it 
        let dashPattern: [CGFloat] = [4.0, 4.0]
        path.setLineDash(dashPattern, count: dashPattern.count, phase: 0)
        // <<
        path.lineWidth = 1
        path.move(to: CGPoint(x: 0, y: 0))
        path.addLine(to: CGPoint(x: 100, y: 100))
        path.stroke()

    }
}

正如本帖多次提到的,您可以通过调整patternphase来实现复杂的虚线效果。

希望这能帮到您。


6

首先,所有的功劳归功于RuiAAPeres和Prince,我只是将他们的答案封装到一个UIView对象中,供其他人在其项目中使用。

#import <UIKit/UIKit.h>

/**
 *  Simple UIView for a dotted line
 */
@interface H3DottedLine : UIView

/**
 *  Set the line's thickness
 */
@property (nonatomic, assign) CGFloat thickness;

/**
 *  Set the line's color
 */
@property (nonatomic, copy) UIColor *color;

/**
 *  Set the length of the dash
 */
@property (nonatomic, assign) CGFloat dashedLength;

/**
 *  Set the gap between dashes
 */
@property (nonatomic, assign) CGFloat dashedGap;

@end




@implementation H3DottedLine

#pragma mark - Object Lifecycle

- (instancetype)init {
    self = [super init];

    if (self) {
        // Set Default Values
        _thickness = 1.0f;
        _color = [UIColor whiteColor];
        _dashedGap = 1.0f;
        _dashedLength = 5.0f;
    }

    return self;
}

#pragma mark - View Lifecycle

- (void)layoutSubviews {
    // Note, this object draws a straight line. If you wanted the line at an angle you simply need to adjust the start and/or end point here.
    [self updateLineStartingAt:self.frame.origin andEndPoint:CGPointMake(self.frame.origin.x+self.frame.size.width, self.frame.origin.y)];
}

#pragma mark - Setters

- (void)setThickness:(CGFloat)thickness {
    _thickness = thickness;
    [self setNeedsLayout];
}

- (void)setColor:(UIColor *)color {
    _color = [color copy];
    [self setNeedsLayout];
}

- (void)setDashedGap:(CGFloat)dashedGap {
    _dashedGap = dashedGap;
    [self setNeedsLayout];
}

- (void)setDashedLength:(CGFloat)dashedLength {
    _dashedLength = dashedLength;
    [self setNeedsLayout];
}

#pragma mark - Draw Methods

-(void)updateLineStartingAt:(CGPoint)beginPoint andEndPoint:(CGPoint)endPoint {

    // Important, otherwise we will be adding multiple sub layers
    if ([[[self layer] sublayers] objectAtIndex:0]) {
        self.layer.sublayers = nil;
    }

    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    [shapeLayer setBounds:self.bounds];
    [shapeLayer setPosition:self.center];
    [shapeLayer setFillColor:[UIColor clearColor].CGColor];
    [shapeLayer setStrokeColor:self.color.CGColor];
    [shapeLayer setLineWidth:self.thickness];
    [shapeLayer setLineJoin:kCALineJoinRound];
    [shapeLayer setLineDashPattern:@[@(self.dashedLength), @(self.dashedGap)]];

    // Setup the path
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, beginPoint.x, beginPoint.y);
    CGPathAddLineToPoint(path, NULL, endPoint.x, endPoint.y);

    [shapeLayer setPath:path];
    CGPathRelease(path);

    [[self layer] addSublayer:shapeLayer];
}

@end

对于无法使此类工作的人,请尝试将所有self.frame实例更改为self.bounds,并将[shapeLayer setPosition:self.center]设置为[shapeLayer setPosition:CGPointZero]。 - Bryan P

4

Swift 5 & 使用扩展 :

使用以下代码,您可以在视图中央绘制虚线。

extension UIView {
    enum dashedOrientation {
        case horizontal
        case vertical
    }
    
    func makeDashedBorderLine(color: UIColor, strokeLength: NSNumber, gapLength: NSNumber, width: CGFloat, orientation: dashedOrientation) {
        let path = CGMutablePath()
        let shapeLayer = CAShapeLayer()
        shapeLayer.lineWidth = width
        shapeLayer.strokeColor = color.cgColor
        shapeLayer.lineDashPattern = [strokeLength, gapLength]
        if orientation == .vertical {
            path.addLines(between: [CGPoint(x: bounds.midX, y: bounds.minY),
                                    CGPoint(x: bounds.midX, y: bounds.maxY)])
        } else if orientation == .horizontal {
            path.addLines(between: [CGPoint(x: bounds.minX, y: bounds.midY),
                                    CGPoint(x: bounds.maxX, y: bounds.midY)])
        }
        shapeLayer.path = path
        layer.addSublayer(shapeLayer)
    }
}

调用方法:

vu1.makeDashedBorderLine(color: .black, strokeLength: 7, gapLength: 5, width: 2, orientation: .horizontal)

Horizontal Vertical


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接