Tracez une ligne en pointillé (pas en pointillés!), Avec IBDesignable en 2017


86

Il est facile de tracer une ligne pointillée avec UIKit. Donc:

CGFloat dashes[] = {4, 2};
[path setLineDash:dashes count:2 phase:0];
[path stroke];

entrez la description de l'image ici

Existe-t-il un moyen de dessiner une véritable ligne pointillée?

entrez la description de l'image ici

Des idées?


Puisque cette question est vraiment ancienne et que personne n'a apporté de @IBDesignablesolution complète , la voici ...

J'espère que cela permet à quelqu'un d'économiser de la frappe.

@IBDesignable class DottedVertical: UIView {

    @IBInspectable var dotColor: UIColor = UIColor.etc
    @IBInspectable var lowerHalfOnly: Bool = false

    override func draw(_ rect: CGRect) {

        // say you want 8 dots, with perfect fenceposting:
        let totalCount = 8 + 8 - 1
        let fullHeight = bounds.size.height
        let width = bounds.size.width
        let itemLength = fullHeight / CGFloat(totalCount)

        let path = UIBezierPath()

        let beginFromTop = CGFloat(0.0)
        let top = CGPoint(x: width/2, y: beginFromTop)
        let bottom = CGPoint(x: width/2, y: fullHeight)

        path.move(to: top)
        path.addLine(to: bottom)

        path.lineWidth = width

        let dashes: [CGFloat] = [itemLength, itemLength]
        path.setLineDash(dashes, count: dashes.count, phase: 0)

        // for ROUNDED dots, simply change to....
        //let dashes: [CGFloat] = [0.0, itemLength * 2.0]
        //path.lineCapStyle = CGLineCap.round

        dotColor.setStroke()
        path.stroke()
    }
}

Je l'ai fait verticalement, vous pouvez facilement changer.

entrez la description de l'image ici

Mettez simplement un UIView dans la scène; faites-en la largeur que vous souhaitez et ce sera la largeur de la ligne pointillée.

Changez simplement la classe en DottedVerticalet vous avez terminé. Il sera rendu comme ça correctement dans le storyboard.

entrez la description de l'image ici

Notez que l'exemple de code donné pour la hauteur des blocs ("totalCount" et ainsi de suite ...) donne les blocs parfaitement, au pixel, en correspondance avec les extrémités de l'UIView qui crée la ligne.

Assurez-vous de cocher la réponse de RobMayoff ci-dessous, qui donne les deux lignes de code nécessaires pour les points-pas-blocs.


voici une façon fantastique de dessiner des lignes diagonales! :) stackoverflow.com/a/45228178/294884
Fattie

Merci Fattie, j'ai utilisé avec peu de modifications pour que les points comptent selon la hauteur de vue, laissez totalDynamicDots = bounds.size.height / CGFloat (3); let itemLength = fullHeight / totalDynamicDots
Nitesh

pouvons-nous en avoir une version horizontale? @Fattie
Mohmmad S

Réponses:


97

Définissez le style de la casquette de ligne sur arrondi et définissez la longueur «on» sur un petit nombre.

Exemple de terrain de jeu Swift:

import UIKit
import PlaygroundSupport

let path = UIBezierPath()
path.move(to: CGPoint(x:10,y:10))
path.addLine(to: CGPoint(x:290,y:10))
path.lineWidth = 8

let dashes: [CGFloat] = [0.001, path.lineWidth * 2]
path.setLineDash(dashes, count: dashes.count, phase: 0)
path.lineCapStyle = CGLineCap.round

UIGraphicsBeginImageContextWithOptions(CGSize(width:300, height:20), false, 2)

UIColor.white.setFill()
UIGraphicsGetCurrentContext()!.fill(.infinite)

UIColor.black.setStroke()
path.stroke()

let image = UIGraphicsGetImageFromCurrentImageContext()
let view = UIImageView(image: image)
PlaygroundPage.current.liveView = view

UIGraphicsEndImageContext()

Résultat:

points


Pour objective-C, en utilisant le même exemple de classe que dans la question, ajoutez simplement

CGContextSetLineCap(cx, kCGLineCapRound);

avant l'appel à CGContextStrokePath, et modifiez les ravaleurs du tableau pour qu'elles correspondent à mon code Swift.


9
Les informations clés se trouvent dans la première ligne (texte anglais) de ma réponse. Le reste est de la sauce.
rob mayoff

5
J'ai trouvé que définir la longueur sur 0.01vous donne un point circulaire, alors qu'ils sont légèrement allongés lors de l'utilisation 0.
James P

J'ai créé cette image en prenant une capture d'écran (raccourci par défaut à l'échelle du système: ⌘⇧4). Il n'y a jamais eu de fonction de capture intégrée dans Xcode à ma connaissance.
rob mayoff

2
Les conseils de James P sont inestimables. Ce bug m'a causé tant de maux de cœur et de travail. Merci James. Je vais créer une demi-réponse pour que les gens puissent la voir plus clairement.
Womble

1
J'ai mis à jour ma réponse avec la solution de contournement du bogue et la dernière syntaxe Swift.
rob mayoff

13

Version Objective-C de l'exemple Swift ci-dessus:

UIBezierPath * path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointMake(10.0, 10.0)];
[path addLineToPoint:CGPointMake(290.0, 10.0)];
[path setLineWidth:8.0];
CGFloat dashes[] = { path.lineWidth, path.lineWidth * 2 };
[path setLineDash:dashes count:2 phase:0];
[path setLineCapStyle:kCGLineCapRound];
UIGraphicsBeginImageContextWithOptions(CGSizeMake(300, 20), false, 2);
[path stroke];
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

11

En utilisant une extension UIView, compatible avec Swift 3.0, les éléments suivants devraient fonctionner:

extension UIView {

    func addDashedBorder(strokeColor: UIColor, lineWidth: CGFloat) {
        self.layoutIfNeeded()
        let strokeColor = strokeColor.cgColor

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

        shapeLayer.bounds = shapeRect
        shapeLayer.position = CGPoint(x: frameSize.width/2, y: frameSize.height/2)
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = strokeColor
        shapeLayer.lineWidth = lineWidth
        shapeLayer.lineJoin = kCALineJoinRound

        shapeLayer.lineDashPattern = [5,5] // adjust to your liking
        shapeLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: shapeRect.width, height: shapeRect.height), cornerRadius: self.layer.cornerRadius).cgPath

        self.layer.addSublayer(shapeLayer)
    }

}

Ensuite, dans une fonction qui s'exécute après viewDidLoad, comme viewDidLayoutSubviews, exécutez la addDashedBorderfonction sur la vue en question:

class ViewController: UIViewController {

    var someView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        someView = UIView()
        someView.layer.cornerRadius = 5.0

        view.addSubview(someView)

        someView.translatesAutoresizingMaskIntoConstraints = false
        someView.widthAnchor.constraint(equalToConstant: 200).isActive = true
        someView.heightAnchor.constraint(equalToConstant: 200).isActive = true
        someView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        someView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
    }

    override func viewDidLayoutSubviews() {
        someView.addDashedBorder(strokeColor: UIColor.red, lineWidth: 1.0)
    }

}

1
Cela crée une ligne en pointillés (c'est-à-dire des rectangles), mais comment créer une ligne en pointillé (c'est-à-dire des cercles)?
Crashalot

4

Bonjour les gars, cette solution a bien fonctionné pour moi. J'ai trouvé quelque part et j'ai changé un peu pour éviter les avertissements de la console.

extension UIImage {
    static func drawDottedImage(width: CGFloat, height: CGFloat, color: UIColor) -> UIImage {
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 1.0, y: 1.0))
        path.addLine(to: CGPoint(x: width, y: 1))
        path.lineWidth = 1.5           
        let dashes: [CGFloat] = [path.lineWidth, path.lineWidth * 5]
        path.setLineDash(dashes, count: 2, phase: 0)
        path.lineCapStyle = .butt
        UIGraphicsBeginImageContextWithOptions(CGSize(width: width, height: height), false, 2)
        color.setStroke()
        path.stroke()

        let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()

        return image
    }
}

Voici le résultat:

résultat


3

Je travaille un peu sur la solution acceptée par rob mayoff pour personnaliser facilement la ligne pointillée:

  • changez le rayon de chaque cercle.
  • changer le nombre d'espaces entre 2 cercles.
  • modifiez le nombre de motifs à générer.

La fonction renvoie une UIImage:

extension UIImage {

    class func dottedLine(radius radius: CGFloat, space: CGFloat, numberOfPattern: CGFloat) -> UIImage {


        let path = UIBezierPath()
        path.moveToPoint(CGPointMake(radius/2, radius/2))
        path.addLineToPoint(CGPointMake((numberOfPattern)*(space+1)*radius, radius/2))
        path.lineWidth = radius

        let dashes: [CGFloat] = [path.lineWidth * 0, path.lineWidth * (space+1)]
        path.setLineDash(dashes, count: dashes.count, phase: 0)
        path.lineCapStyle = CGLineCap.Round


        UIGraphicsBeginImageContextWithOptions(CGSizeMake((numberOfPattern)*(space+1)*radius, radius), false, 1)
        UIColor.whiteColor().setStroke()
        path.stroke()
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return image

    }
}

Et voici comment obtenir l'image:

UIImage.dottedLine(radius: 100, space: 2, numberOfPattern: 1)

2

Pas une réponse complète, juste un truc très important que James P a soulevé dans un commentaire sur la réponse préférée:

Il a écrit:

J'ai trouvé que régler la longueur sur 0,01 vous donne un point circulaire, alors qu'ils sont légèrement allongés lorsque vous utilisez 0.

Par exemple,

   let dashes: [CGFloat] = [0.001, path.lineWidth * 2]

0

Dans swift 3.1, vous pouvez utiliser le code ci-dessous:

context.setLineCap(.round)

Avoir trois styles:

 /* Line cap styles. */

public enum CGLineCap : Int32 {

    case butt

    case round

    case square
}

0

Fonctionnant bien avec le code ci-dessous,

layer.path = linePath.cgPath
layer.lineWidth = 3
layer.lineDashPattern = [1,layer.lineWidth*2] as [NSNumber]
layer.lineCap = "round"

0

Hé, il est peut-être trop tard pour répondre à cette question. mais si vous êtes d'accord, j'aimerais partager un moyen simple de résoudre ce problème avec les développeurs qui seront peut-être confrontés à ce problème à l'avenir. donc je suppose que la solution la plus simple en utilisant @IBDesignable. Vous avez juste besoin de créer cette classe

import UIKit

@IBDesignable class DottedVertical: UIView {

    @IBInspectable var dotColor: UIColor = UIColor.red
    @IBInspectable var lowerHalfOnly: Bool = false

    override func draw(_ rect: CGRect) {

        // say you want 8 dots, with perfect fenceposting:
        let totalCount = 8 + 8 - 1
        let fullHeight = bounds.size.height
        let width = bounds.size.width
        let itemLength = fullHeight / CGFloat(totalCount)

        let path = UIBezierPath()

        let beginFromTop = CGFloat(0.0)
        let top = CGPoint(x: width/2, y: beginFromTop)
        let bottom = CGPoint(x: width/2, y: fullHeight)

        path.move(to: top)
        path.addLine(to: bottom)

        path.lineWidth = width
        //DASHED SIMPLE LINE
        //let dashes: [CGFloat] = [itemLength, itemLength]
        //path.setLineDash(dashes, count: dashes.count, phase: 0)

        // for ROUNDED dots, simply change to....
        let dashes: [CGFloat] = [0.0, itemLength * 1.1]
        path.lineCapStyle = CGLineCap.round
        path.setLineDash(dashes, count: dashes.count, phase: 0)

        dotColor.setStroke()
        path.stroke()
    }
}

Et puis ajoutez-le à votre vue dans le storyboard comme ça entrez la description de l'image ici

Une fois que vous avez terminé, personnalisez à froid l'espace entre les calques de cette ligne let dashes: [CGFloat] = [0.0, itemLength * 1.1] -> Ligne 39 dans la classe DottedVertical. ou si vous souhaitez personnaliser la largeur du calque, il vous suffit de modifier la largeur de votre vue en ligne à partir de votre storyboard


-1

J'ai implémenté le morceau de code suivant pour ajouter une bordure avec un style pointillé en bas de titleLabel( UILabel) dans viewDidAppear:

CAShapeLayer *shapelayer = [CAShapeLayer layer];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0.0, titileLabel.frame.size.height-2)];
[path addLineToPoint:CGPointMake(SCREEN_WIDTH, titileLabel.frame.size.height-2)];
UIColor *fill = [UIColor colorWithRed:0.80f green:0.80f blue:0.80f alpha:1.00f];
shapelayer.strokeStart = 0.0;
shapelayer.strokeColor = fill.CGColor;
shapelayer.lineWidth = 2.0;
shapelayer.lineJoin = kCALineJoinRound;
shapelayer.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:2],[NSNumber numberWithInt:3 ], nil];
shapelayer.path = path.CGPath;

[titileLabel.layer addSublayer:shapelayer];

Réfrence: https://gist.github.com/kaiix/4070967


cela produit des carrés, pas des cercles, lorsque j'utilise votre code.
bonjourB

essayez de modifier les valeurs de moveToPoint, addLineToPoint, linewidth et lineDashPattern selon vos besoins
iAkshay
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.