Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
573 views
in Technique[技术] by (71.8m points)

ios - Centering CAShapeLayer within UIView Swift

I'm having trouble centering CAShapeLayer within a UIView. I've search and most solutions are from pre 2015 in Obj C or haven't been solved.

Attached is what the image looks like. When I inspect it, its inside the red view, but idk why its not centering. I've tried resizing it but still doesn't work.

image

let progressView: UIView = {
    let view = UIView()
    view.backgroundColor = .red
    return view
}()

//MARK: - ViewDidLoad
override func viewDidLoad() {
    super.viewDidLoad()

    view.addSubview(progressView)
progressView.anchor(top: nil, left: nil, bottom: nil, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 200, height: 200)
    progressView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    progressView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true

    setupCircleLayers()
}

var shapeLayer: CAShapeLayer!

private func setupCircleLayers() {
    let trackLayer = createCircleShapeLayer(strokeColor: UIColor.rgb(red: 56, green: 25, blue: 49, alpha: 1), fillColor: #colorLiteral(red: 0.9686274529, green: 0.78039217, blue: 0.3450980484, alpha: 1))
    progressView.layer.addSublayer(trackLayer)
}

    private func createCircleShapeLayer(strokeColor: UIColor, fillColor: UIColor) -> CAShapeLayer {

    let centerpoint = CGPoint(x: progressView.frame.width / 2, y: progressView.frame.height / 2)
    let circularPath = UIBezierPath(arcCenter: centerpoint, radius: 100, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
    let layer = CAShapeLayer()
    layer.path = circularPath.cgPath
    layer.fillColor = fillColor.cgColor
    layer.lineCap = kCALineCapRound
    layer.position = progressView.center
    return layer
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

As @ukim says, your problem is that you are trying to determine the position of your layer, based on views and their size before these are finite.

When you are in viewDidLoad you don't know the size and final position of your views yet. You can add the progressView alright but you can not be sure that its size or position are correct until viewDidLayoutSubviews (documented here).

So, if I move your call to setupCircleLayers to viewDidLayoutSubviews and I change the centerpoint to CGPoint.zero and alter the calculation of your layer.position to this:

layer.position = CGPoint(x: progressView.frame.size.width / 2, y: progressView.frame.size.height / 2)

Then I see this:

enter image description here

Which I hope is more what you were aiming for.

Here is the complete listing (note that I had to change some of your methods as I didn't have access to anchor or UIColor.rgb for instance but you can probably work your way around that :))

import UIKit

class ViewController: UIViewController {

    var shapeLayer: CAShapeLayer!
    let progressView: UIView = {
        let view = UIView()
        view.backgroundColor = .red
        return view
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

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

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        if shouldAddSublayer {
            setupCircleLayers()
        }
    }

    private func setupCircleLayers() {
        let trackLayer = createCircleShapeLayer(strokeColor: UIColor.init(red: 56/255, green: 25/255, blue: 49/255, alpha: 1), fillColor: #colorLiteral(red: 0.9686274529, green: 0.78039217, blue: 0.3450980484, alpha: 1))
        progressView.layer.addSublayer(trackLayer)
    }

    private var shouldAddSublayer: Bool {
        /*
         check if:
         1. we have any sublayers at all, if we don't then its safe to add a new, so return true
         2. if there are sublayers, see if "our" layer is there, if it is not, return true 
        */
        guard let sublayers = progressView.layer.sublayers else { return true }
        return sublayers.filter({ $0.name == "myLayer"}).count == 0
    }

    private func createCircleShapeLayer(strokeColor: UIColor, fillColor: UIColor) -> CAShapeLayer {
        let centerpoint = CGPoint.zero
        let circularPath = UIBezierPath(arcCenter: centerpoint, radius: 100, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
        let layer = CAShapeLayer()
        layer.path = circularPath.cgPath
        layer.fillColor = fillColor.cgColor
        layer.lineCap = kCALineCapRound
        layer.position = CGPoint(x: progressView.frame.size.width / 2, y: progressView.frame.size.height / 2)
        layer.name = "myLayer"
        return layer
    }
}

Hope that helps.

Caveat

When you do the above, that also means that every time viewDidLayoutSubviews is called, you are adding a new layer. To circumvent that, you can use the name property of a layer

layer.name = "myLayer"

and then check if you have already added your layer. Something like this should work:

private var shouldAddSublayer: Bool {
    /*
     check if:
     1. we have any sublayers at all, if we don't then its safe to add a new, so return true
     2. if there are sublayers, see if "our" layer is there, if it is not, return true

    */
    guard let sublayers = progressView.layer.sublayers else { return true }
    return sublayers.filter({ $0.name == "myLayer"}).count == 0
}

Which you then use here:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if shouldAddSublayer {
        setupCircleLayers()
    }
}

I've updated the listing.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...