web-dev-qa-db-fra.com

UIView avec une ligne pointillée

Ce que j'ai:

enter image description here

Pour créer cette ligne, j'ai essentiellement un UIView et je fais ce qui suit:

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

Remarque: Ce code a été trouvé ici sur stackoverflow, donc si quelqu'un peut me donner la référence, j'apprécierais.

Ce que je veux:

enter image description here

Ok donc la "seule" chose dont j'ai besoin est de créer ce modèle sur le UIView. Je sais que je suis capable de le faire en utilisant Quartz2D (un moyen simple de le faire peut être trouvé ici ). Mais je veux le faire en manipulant le CALayer et en ne passant pas à la méthode draw. Pourquoi? En raison de la transformation que j'effectue sur mon UIView, je ne peux pas dessiner correctement à l'aide de la méthode draw.

Modifier 1:

Juste pour illustrer mon problème:

enter image description here

Normalement, ce que vous avez est UIView et vous dessinez simplement quelque chose dedans (dans ce cas, une simple ligne). La solution que j'ai trouvée pour se débarrasser de la zone "grise" était de, au lieu de dessiner quelque chose, simplement transformer le UIView lui-même. Cela fonctionne bien, si vous voulez une ligne entièrement remplie, le problème survient lorsque vous en voulez une en pointillés.

26
Rui Peres

Note: Le code de Prince m'a vraiment aidé, donc je lui donnerai +10 pour les conseils. Mais au final, j'ajoute de venir avec mon propre code. Je vais également y ajouter un peu de contexte, donc cela peut être utile pour les futurs lecteurs


Le code final était comme ceci:

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

Dans mon cas, le beginPoint et le endPoint sont déplaçables par l'utilisateur, en utilisant KVO. Donc quand l'un d'eux bouge:

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqual:@"position"])
    {
        [self updateLine];
    }
}

J'ai beaucoup joué avec le code de Prince. J'ai essayé le draw: méthode, qui ajoute une fine ligne entre la ligne pointillée (un peu bizarre ...) et j'ai aussi essayé initWithFrame:. En soi, son code, sans aucune modification, me donnerait ce genre d'erreurs sur la console:

<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
24
Rui Peres

Vérifiez UIBezierPath setLineDash:count:phase:méthode :

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

Cela vous permet de dessiner des lignes en pointillés.

  1. Ajoutez d'abord un CAShapeLayer. Ajoutez-le comme sous-couche à votre UIView. Il a une propriété path.
  2. Faites maintenant un objet de UIBezierPath. Tracez la ligne en utilisant setLineDash.

Par exemple:

 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;

Remarque: Cette réponse fournit un indice pour que vous puissiez improviser en fonction de vos exigences.

39
Paresh Navadiya

Ligne de tiret dans Swift4 • Xcode 9

Créez un CAShapeLayer et utilisez 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)
    }
}

Utilisation:

dashView.addDashedBorder()

Sortie:

enter image description here

19
Jack

Voici la réponse de Swift 3 de Alexandre G https://stackoverflow.com/ a/38194152/1800489

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)
        }
}
14
moody

Swift 2.2

déposer cela ici pour gagner du temps aux autres ..

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)
    }
}
14
Alexandre G

La réponse acceptée a un problème de coordonnées. La ligne sera tracée à une certaine distance ci-dessous. Et je ne peux pas comprendre pourquoi et combien de distance il augmente sur la coordonnée Y.

Il existe un moyen de dessiner une ligne pointillée avec les coordonnées correctes:

-(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);
}

Cette réponse est de Dessinez la ligne pointillée (non pointillée!), Avec IBDesignable en 2017 . N'OUBLIEZ PAS N'OUBLIEZ PAS de définir la couleur d'arrière-plan comme blanc lorsque vous voulez une ligne pointillée noire !! Par défaut, la vue a une couleur d'arrière-plan noire et la couleur de la ligne est également noire, donc je pensais que c'était une ligne continue. Cela m'a coûté une demi-journée pour le découvrir. T_T

7
DianeZhou

Tout d'abord, tout le mérite revient à RuiAAPeres et Prince, j'encapsule simplement leurs réponses dans un objet UIView que d'autres peuvent déposer dans leurs projets et utiliser

#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
6
Shaheen Ghiassy

Mettre à jour Swift 5 & IBezierPath

Pour ceux qui travaillent avec UIBezierPath au lieu de CAShapeLayer, voici comment y parvenir

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()

    }
}

Comme dit plusieurs fois dans ce fil, vous pouvez jouer avec le pattern et le phase pour obtenir une ligne pointillée complexe.

J'espère que cela t'aides

1
Olympiloutre