在 UIView 中居中 CAShapeLayer Swift

Centering CAShapeLayer within UIView Swift

我无法在 UIView 中将 CAShapeLayer 居中。我搜索过,大多数解决方案来自 2015 年前的 Obj C 或尚未解决。

附件是图片的样子。当我检查它时,它在红色视图内,但我不知道为什么它不居中。我试过调整它的大小,但还是不行。

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
}

您在 viewDidLoad() 中呼叫 setupCircleLayers()。当时,progressView.frame 还没有从约束中计算出来。

尝试

let centerpoint = CGPoint(x: 100, y: 100)

而不是从 progressView.frame

计算值

你可以在你的 playground 上试试这个:

import UIKit
import PlaygroundSupport


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

    var layer: CAShapeLayer?


    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(progressView)
        progressView.translatesAutoresizingMaskIntoConstraints = false
        progressView.backgroundColor = .red
        progressView.widthAnchor.constraint(equalToConstant: 200).isActive = true
        progressView.heightAnchor.constraint(equalToConstant: 200).isActive = true
        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: .red, 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: 100, y: 100)
        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
        return layer
    }
}

let containerView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 375.0, height: 667.0))
let vc = MyVC()
PlaygroundPage.current.liveView = vc.view

正如@ukim 所说,您的问题是您试图根据视图及其在有限之前的大小来确定图层的位置。

当您处于 viewDidLoad 时,您还不知道视图的大小和最终位置。您可以添加 progressView 好,但您不能确定它的大小或位置是否正确,直到 viewDidLayoutSubviews(已记录 here)。

因此,如果我将您对 setupCircleLayers 的呼叫移动到 viewDidLayoutSubviews 并将中心点更改为 CGPoint.zero 并将您的 layer.position 的计算更改为:

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

然后我看到这个:

我希望这更多是你的目标。

这是完整的清单(请注意,我不得不更改您的一些方法,因为我无法访问 anchorUIColor.rgb 等,但您可能可以自己解决那:))

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({ [=11=].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
    }
}

希望对您有所帮助。

警告

当您执行上述操作时,这也意味着 每次 viewDidLayoutSubviews 被调用时,您都在添加一个新层。为避免这种情况,您可以使用图层

name 属性
layer.name = "myLayer"

然后检查您是否已经添加了图层。这样的事情应该有效:

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({ [=13=].name == "myLayer"}).count == 0
}

然后你在这里使用:

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

我更新了列表。