iosswiftchartsuibezierpathcashapelayer

uiview on the edge of UIBezierPath


i am trying to add an overlay (marker view) along side my path created with UIBezierPath. i can achieve it with hardcoded values comparing x's and y's of the graph. but problem is this is getting too much hardcoded as there will be different values expected and i cannot (should not) do such hardcoded.

center.x + outerRadius * cos(startAngle)

i get the location of the next part of the chart by this, but i cannot place my UIView on a right place.

is there anyway i can place a uiview on a UIBezierPath and adjust its direction as per needed (like left right or below or top) of the chart ?

enter image description here


Solution

  • As far as I can tell, your UIBezierPaths are parts of your outer circle. I assume you draw them using startAngle and endAngle. I think you could draw an imaginary circle (which has the same center as yours, and radius of (your inner circle + width of your outer UIBezierPath) / 2 and put your UILabel's corner on it.

    Imaginary circle

    Here's my implementation of an imaginary circle:

    My implementation

    import UIKit
    
    class CircleSliderViewController: UIViewController {
    
        private let circleLayer = CAShapeLayer()
        private let slider = UISlider()
        private let movingLabel = UILabel()
        private let circleRadius: CGFloat = 120
    
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = .white
            setupCircle()
            setupSlider()
            setupLabel()
            updateLabelPosition(angle: 0)
        }
    
        private func setupCircle() {
            let center = view.center
            let circlePath = UIBezierPath(arcCenter: center,
                                          radius: circleRadius,
                                          startAngle: 0,
                                          endAngle: 2 * .pi,
                                          clockwise: true)
            circleLayer.path = circlePath.cgPath
            circleLayer.strokeColor = UIColor.gray.cgColor
            circleLayer.fillColor = UIColor.clear.cgColor
            circleLayer.lineWidth = 2
            view.layer.addSublayer(circleLayer)
        }
    
        private func setupSlider() {
            slider.minimumValue = 0
            slider.maximumValue = 360
            slider.value = 0
            slider.addTarget(self, action: #selector(sliderChanged(_:)), for: .valueChanged)
            slider.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(slider)
    
            NSLayoutConstraint.activate([
                slider.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40),
                slider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40),
                slider.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -40)
            ])
        }
    
        private func setupLabel() {
            movingLabel.text = "Label"
            movingLabel.textAlignment = .center
            movingLabel.backgroundColor = UIColor.systemBlue
            movingLabel.textColor = UIColor.white
            movingLabel.layer.cornerRadius = 8
            movingLabel.clipsToBounds = true
            movingLabel.frame.size = CGSize(width: 60, height: 30)
            view.addSubview(movingLabel)
        }
    
        @objc private func sliderChanged(_ sender: UISlider) {
            let angle = CGFloat(sender.value) * .pi / 180
            updateLabelPosition(angle: angle)
        }
    
        private func updateLabelPosition(angle: CGFloat) {
            let center = view.center
            let xPosition = center.x + (circleRadius + movingLabel.frame.width / 2) * cos(angle)
            let yPosition = center.y + (circleRadius + movingLabel.frame.height / 2) * sin(angle)
            movingLabel.center = CGPoint(x: xPosition, y: yPosition)
        }
    }