Đường nét đứt dùng cho: form nhập liệu (dotted line signature), navigation route, divider, highlight zone...
Cách làm: CAShapeLayer + lineDashPattern
Bước 1: Tạo dự án
Xcode → Single View App → Tên: "DottedLine".
Bước 2: Storyboard
Thêm UIView (container cho line) → Tạo @IBOutlet weak var dottedView: UIView!

Bước 3: Extension UIView
extension UIView {
func drawDottedLine(width: CGFloat = 1.0,
color: UIColor = .systemBlue,
dashPattern: [NSNumber] = [4, 4],
direction: DottedLineDirection = .horizontal) {
// Xóa layer cũ (tránh vẽ đè khi gọi lại)
layer.sublayers?.filter { $0 is CAShapeLayer }.forEach { $0.removeFromSuperlayer() }
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = color.cgColor
shapeLayer.lineWidth = width
shapeLayer.lineDashPattern = dashPattern // [dash, gap, dash, gap...]
shapeLayer.fillColor = UIColor.clear.cgColor
let path = CGMutablePath()
let startPoint: CGPoint
let endPoint: CGPoint
switch direction {
case .horizontal:
startPoint = CGPoint(x: 0, y: bounds.midY)
endPoint = CGPoint(x: bounds.width, y: bounds.midY)
case .vertical:
startPoint = CGPoint(x: bounds.midX, y: 0)
endPoint = CGPoint(x: bounds.midX, y: bounds.height)
}
path.move(to: startPoint)
path.addLine(to: endPoint)
shapeLayer.path = path
layer.addSublayer(shapeLayer)
}
}
enum DottedLineDirection {
case horizontal
case vertical
}
Bước 4: Gọi trong viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
// Đường ngang, cyan, nét 5, gap 3
dottedView.drawDottedLine(width: 5.0, color: .systemCyan, dashPattern: [5, 3])
// Ví dụ khác: đường dọc, đỏ, nét 10 gap 5
// verticalView.drawDottedLine(width: 2, color: .systemRed, dashPattern: [10, 5], direction: .vertical)
}

Các pattern dashPattern phổ biến
| Pattern | Hiệu ứng |
|---|---|
[2, 2] |
Chấm nhỏ dày đặc |
[4, 4] |
Nét đứt chuẩn (default) |
[10, 5] |
Nét dài, khoảng cách trung bình |
[20, 10, 5, 10] |
Pattern phức tạp: nét dài - gap - nét ngắn - gap |
[0, 10] |
Chấm tròn (cần lineCap = .round) |
Nâng cao: Vẽ đường cong/dị dạng
func drawCurvedDottedLine(in view: UIView, points: [CGPoint], color: UIColor = .systemBlue) {
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = color.cgColor
shapeLayer.lineWidth = 2
shapeLayer.lineDashPattern = [8, 4]
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineCap = .round
let path = CGMutablePath()
path.move(to: points[0])
for i in 1..
Animation đường nét đứt (Marching Ants)
func animateDottedLine(_ shapeLayer: CAShapeLayer) {
let animation = CABasicAnimation(keyPath: "lineDashPhase")
animation.fromValue = 0
animation.toValue = shapeLayer.lineDashPattern?.reduce(0) { $0 + $1.intValue } ?? 10
animation.duration = 1.0
animation.repeatCount = .infinity
shapeLayer.add(animation, forKey: "marchingAnts")
}
Lưu ý
- Gọi trong
viewDidLayoutSubviewshoặclayoutSubviewsnếu view size thay đổi (Auto Layout) - Xóa layer cũ trước khi vẽ mới tránh memory leak / visual glitch
lineCap = .roundcho chấm tròn đẹp mắt- Performance: CAShapeLayer render GPU, rất nhẹ