Home > Blockchain >  Rendering text in the middle of a Dashed Line created with UIBezierPath in Swift
Rendering text in the middle of a Dashed Line created with UIBezierPath in Swift

Time:09-26

I’m doing some tests with rendering dashed / dotted lines like so:

enter image description here

For the dotted lines, as an example; I created them with this function:

func drawDottedLines() -> UIImage { let path = UIBezierPath() path.move(to: CGPoint(x: 10,y: 10)) path.addLine(to: CGPoint(x: 290,y: 10)) path.lineWidth = 8

let dots: [CGFloat] = [
    0.01, // dot
    path.lineWidth * 2, // gap
]

path.setLineDash(dots, count: dots.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() ?? nil
UIGraphicsEndImageContext()
return image!

}

What I need to figure out how to do next, render some text in the middle of the lines, so they render like:

 * * * * TEST * * * *

How would I do that?

CodePudding user response:

Not sure why you're generating a UIImage of the dashed-line, as you could easily use a CAShapeLayer ... but anyway ...

The most straight-forward way to do this would be to overlay a label - slightly wider than the text:

enter image description here

and give it the same background color:

enter image description here

However, as you can see, depending on where your dots/dashes fall, you can get sharp edges.

If that's not acceptable, we can create a UIView subclass to draw the dashed-line-pattern on the left-side, then mirror it horizontally and draw it on the right side.

Here's a quick example to get you started:

class DashedLineLabelView: UIView {
    
    public var text: String = "" {
        didSet {
            label.text = text
            setNeedsLayout()
        }
    }
    public var pattern: [NSNumber] = [] {
        didSet {
            setNeedsLayout()
        }
    }
    
    private let label = UILabel()

    private let leftDashLayer = CAShapeLayer()
    private let rightDashLayer = CAShapeLayer()

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    private func commonInit() {
        layer.addSublayer(leftDashLayer)
        leftDashLayer.lineWidth = 8.0
        leftDashLayer.strokeColor = UIColor.black.cgColor
        leftDashLayer.lineCap = .round

        layer.addSublayer(rightDashLayer)
        rightDashLayer.lineWidth = 8.0
        rightDashLayer.strokeColor = UIColor.black.cgColor
        rightDashLayer.lineCap = .round
        
        label.translatesAutoresizingMaskIntoConstraints = false
        addSubview(label)
        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: centerXAnchor),
            label.centerYAnchor.constraint(equalTo: centerYAnchor),
        ])
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()

        // we'll add 8-points "space" on each side of the label/text
        let paddedW: CGFloat = label.frame.width   16.0
        
        // width of lines
        //  we need them inset by one-half the lineWidth
        //  so they don't extend outside the bounds of the view
        let pathW: CGFloat = ((bounds.width - 8.0) - paddedW) * 0.5
        
        let bez = UIBezierPath()
        var pt: CGPoint = CGPoint(x: 4.0, y: bounds.midY)
        bez.move(to: pt)
        pt.x = pathW
        bez.addLine(to: pt)
        
        // same path and dash pattern for left and right dash layers
        leftDashLayer.path = bez.cgPath
        leftDashLayer.lineDashPattern = pattern
        rightDashLayer.path = bez.cgPath
        rightDashLayer.lineDashPattern = pattern
        
        // we need to flip the right-side dash line horizontally
        //  so it is a mirror of the left-side
        //  and move it to the right side
        let tr1 = CATransform3DMakeScale(-1.0, 1.0, 0.0)
        let tr2 = CATransform3DMakeTranslation(pathW * 2.0   paddedW   8.0, 0.0, 0.0)
        rightDashLayer.transform = CATransform3DConcat(tr1, tr2)

    }
}

and an example controller:

class SomeTestVC: UIViewController {

    let testViewA = DashedLineLabelView()
    let testViewB = DashedLineLabelView()

    let sampleStrings: [String] = [
        "Sample Text",
        "ABC",
        "Testing",
        "A Longer String",
    ]
    var strIDX: Int = -1
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // add two Dashed Line Views
        
        testViewA.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(testViewA)
        testViewB.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(testViewB)

        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            testViewA.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            testViewA.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            testViewA.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            testViewA.heightAnchor.constraint(equalToConstant: 60.0),

            testViewB.topAnchor.constraint(equalTo: testViewA.bottomAnchor, constant: 20.0),
            testViewB.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            testViewB.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            testViewB.heightAnchor.constraint(equalToConstant: 60.0),
        ])
        
        testViewA.backgroundColor = .systemYellow
        testViewB.backgroundColor = .systemYellow

        testViewA.pattern = [1, 16]
        testViewB.pattern = [1, 16, 16, 16]

        // set initial text
        changeText()
    }
    
    func changeText() {
        strIDX  = 1
        testViewA.text = sampleStrings[strIDX % sampleStrings.count]
        testViewB.text = sampleStrings[strIDX % sampleStrings.count]
    }
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        changeText()
    }
    
}

It will look like this:

enter image description here

and each tap anywhere will cycle through a few sample strings:

enter image description here

enter image description here

enter image description here

  • Related