簡體   English   中英

多個帶有.round lineCap的CAShapeLayers相互重疊

[英]Multiple CAShapeLayers with .round lineCap are overlapped by each other

我嘗試使用lineCap .round半圓路徑組合多個圖層:

let layer = CAShapeLayer()
layer.lineWidth = 12
layer.lineCap = .round
layer.strokeColor = color.withAlphaComponent(0.32).cgColor
layer.fillColor = UIColor.clear.cgColor
// angles calculation
let path = UIBezierPath(arcCenter: arcCenter,
                        radius: radius,
                        startAngle: startAngle,
                        endAngle: engAngle,
                        clockwise: true)
layer.path = path.cgPath

試圖實現以下目標:

在此處輸入圖像描述

但我的圖層相互重疊。 是否可以以某種方式快速修復它,還是我需要手動實現帶圓角的路徑計算?

在此處輸入圖像描述

.lineCap =.round我們得到一個以端點為中心的“圓”,半徑為線寬的 1/2:

在此處輸入圖像描述

因此,要讓圓形末端位於端點處,我們可以通過asin(lineWidth * 0.5 / radius)調整端點:

在此處輸入圖像描述

假設我們順時針方向:

let delta: CGFloat = lineCap == .round ? asin(lineWidth * 0.5 / radius) : 0.0

let path = UIBezierPath(arcCenter: center,
                        radius: radius,
                        startAngle: startDegrees.radians + delta,
                        endAngle: endDegrees.radians - delta,
                        clockwise: true)

所以,如果我們用.lineEnd =.butt (默認值)形成這個圖像的一系列度數:

在此處輸入圖像描述

我們可以通過偏移開始和結束角度來得到這個:

在此處輸入圖像描述

這是一個完整的示例 class:

class ConnectedArcsView: UIView {
    
    public var segmentDegrees: [CGFloat] = [] {
        didSet {
            var n: Int = 0
            if let subs = layer.sublayers {
                n = subs.count
                if n > segmentDegrees.count {
                    // if we already have sublayers,
                    //  remove any extras
                    for _ in 0..<n - segmentDegrees.count {
                        subs.last?.removeFromSuperlayer()
                    }
                }
            }
            // add sublayers if needed
            while n <  segmentDegrees.count {
                let l = CAShapeLayer()
                l.fillColor = UIColor.clear.cgColor
                layer.addSublayer(l)
                n += 1
            }
            setNeedsLayout()
        }
    }
    
    // segment colors default: [.red, .green, .blue]
    public var segmentColors: [UIColor] = [.red, .green, .blue] { didSet { setNeedsLayout() } }
    
    // line width default: 12
    public var lineWidth: CGFloat = 12 { didSet { setNeedsLayout() } }
    
    // line cap default: .round
    public var lineCap: CAShapeLayerLineCap = .round  { didSet { setNeedsLayout() } }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        guard let subs = layer.sublayers else { return }
        
        let radius = (bounds.size.width - lineWidth) * 0.5
        let center = CGPoint(x: bounds.midX, y: bounds.midY)
        
        // if lineCap == .round
        //  calculate delta for start and end angles
        let delta: CGFloat = lineCap == .round ? asin(lineWidth * 0.5 / radius) : 0.0
        
        // calculate start angle so the "gap" is centered at the bottom
        let totalDegrees: CGFloat = segmentDegrees.reduce(0.0, +)
        var startDegrees: CGFloat = 90.0 + (360.0 - totalDegrees) * 0.5
        
        for i in 0..<segmentDegrees.count {
            let endDegrees = startDegrees + segmentDegrees[i]
            
            guard let shape = subs[i] as? CAShapeLayer else { continue }
            
            shape.lineWidth = lineWidth
            shape.lineCap = lineCap
            shape.strokeColor = segmentColors[i % segmentColors.count].cgColor
            
            let path = UIBezierPath(arcCenter: center,
                                    radius: radius,
                                    startAngle: startDegrees.radians + delta,
                                    endAngle: endDegrees.radians - delta,
                                    clockwise: true)
            
            shape.path = path.cgPath
            
            startDegrees += segmentDegrees[i]
        }
        
    }

}

和一個示例視圖 controller 顯示其用法:

class ExampleViewController: UIViewController {
    
    let testView = ConnectedArcsView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemBlue
        
        let degrees: [CGFloat] = [
            40, 25, 140, 25, 40
        ]
        
        let colors: [UIColor] = [
            .systemRed, .systemYellow, .systemGreen, .systemYellow, .systemRed
        ] //.map { ($0 as UIColor).withAlphaComponent(0.5) }
        
        testView.segmentDegrees = degrees
        testView.segmentColors = colors
        testView.lineWidth = 12
        
        testView.backgroundColor = .black
        testView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(testView)
        
        // add an info label
        let v = UILabel()
        v.textAlignment = .center
        v.numberOfLines = 0
        v.text = "Tap anywhere to toggle between\n\".round\" and \".butt\""
        v.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(v)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            
            testView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            testView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
            testView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
            testView.heightAnchor.constraint(equalTo: testView.widthAnchor),
            
            v.topAnchor.constraint(equalTo: testView.bottomAnchor, constant: 20.0),
            v.widthAnchor.constraint(equalTo: testView.widthAnchor),
            v.centerXAnchor.constraint(equalTo: testView.centerXAnchor),
            
        ])
        
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        testView.lineCap = testView.lineCap == .round ? .butt : .round
    }
    
}

跑步時,點擊任意位置將在 .round 和.round之間.butt


編輯- 忘記包含輔助擴展:

extension CGFloat {
    var degrees: CGFloat {
        return self * CGFloat(180) / .pi
    }
    var radians: CGFloat {
        return self * .pi / 180.0
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM