使用自动布局绘制CAShapeLayer

ran*_*lan 4 uibezierpath autolayout swift

我正在尝试使用CAShapeLayer绘制圆形按钮并为其设置动画,但是仅该绘制让我很头疼-我似乎无法弄清楚如何将数据传递到类中。

这是我的设置:-会绘制CAShapeLayer的UIView类型的类-该视图在我的视图控制器中呈现,并使用自动布局约束构建

我尝试使用layoutIfNeeded,但似乎传递数据的时间太晚,无法绘制视图。我也尝试过在vieWillLayoutSubviews()中重画视图,但是什么也没有。下面的示例代码。我究竟做错了什么?

我是否太早/太迟地传递数据?我绘制bezierPath太晚了吗?

我非常感谢指针。

也许还有第二个后续问题:是否有一种更简单的方法来绘制与其视图大小绑定的圆形路径?

在我的View Controller中:

import UIKit

class ViewController: UIViewController {

    let buttonView: CircleButton = {
        let view = CircleButton()
        view.backgroundColor = .black
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    override func viewWillLayoutSubviews() {
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(buttonView)
        buttonView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        buttonView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        buttonView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.75).isActive = true
        buttonView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.25).isActive = true
        buttonView.layoutIfNeeded()
        buttonView.arcCenter = buttonView.center
        buttonView.radius = buttonView.frame.width/2
    }

    override func viewDidAppear(_ animated: Bool) {
        print(buttonView.arcCenter)
        print(buttonView.radius)
    }
}
Run Code Online (Sandbox Code Playgroud)

以及buttonView的类:

class CircleButton: UIView {

    //Casting outer circular layers
    let trackLayer = CAShapeLayer()
    var arcCenter = CGPoint()
    var radius = CGFloat()


    //UIView Init
    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    //UIView post init
    override func layoutSubviews() {
        super.layoutSubviews()

        print("StudyButtonView arcCenter \(arcCenter)")
        print("StudyButtonView radius \(radius)")

        layer.addSublayer(trackLayer)
        let outerCircularPath = UIBezierPath(arcCenter: arcCenter, radius: radius, startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: true)
        trackLayer.path = outerCircularPath.cgPath
        trackLayer.strokeColor = UIColor.lightGray.cgColor
        trackLayer.lineWidth = 5
        trackLayer.strokeStart = 0
        trackLayer.strokeEnd = 1
        trackLayer.fillColor = UIColor.clear.cgColor
        trackLayer.transform = CATransform3DMakeRotation(-CGFloat.pi/2, 0, 0, 1)
    }


    //Required for subclass
    required init?(coder aDecoder: NSCoder) {
        fatalError("has not been implemented")
    }
}
Run Code Online (Sandbox Code Playgroud)

Rob*_*Rob 5

是不是真的有自动布局和正确实施的之间的任何关联CircleButton类。您的CircleButton班级不知道或不在乎它是通过自动布局配置的,还是具有固定大小的。

你的自动布局代码看起来OK(超过5分和6以下等)。您的代码段中的大多数问题都在您的CircleButton课程中。一些观察:

  1. 如果要旋转形状图层,则也必须设置其形状frame,否则尺寸为.zero,最终将围绕origin视图的角度旋转形状(并在视图的外侧旋转bounds,如果您认为这是问题所在)在剪辑子视图)。确保设置frameCAShapeLayerbounds视图试图转动它。坦白说,我将删除transform,但是鉴于您正在使用strokeStartand strokeEnd,所以我猜您可能稍后要更改这些值,并使其从12点开始,在这种情况下,转换是有意义的。

    底线(如果旋转)设置第frame一个。如果不是,则设置图层的frame是可选的。

  2. 如果要更改视图的属性以更新形状层,则需要确保didSet观察者对形状层进行了适当的更新(或调用setNeedsLayout)。您不希望您的视图控制器不必弄乱形状层的内部结构,但还希望确保这些更改确实反映在形状层中。

  3. 这只是次要的观察,但我建议在添加形状层的过程中init仅配置一次并将其添加到视图层次结构中。这样更有效。因此,让各种init方法调用您自己的configure方法。然后,在中进行大小相关的操作(例如更新路径)layoutSubviews。最后,让属性观察器直接更新形状层。这种分工更加有效。

  4. 如果你愿意,你可以做这个@IBDesignable,把它放在自己的目标在你的项目。然后,您可以在IB中直接添加它,并查看其外观。您还可以设置所有各种属性@IBInspectable,也可以在IB中正确设置它们。然后,如果您不想这样做,则无需在视图控制器的代码中执行任何操作。(但是,如果您愿意,请放心。)

  5. 这是一个小问题,但是当您以编程方式添加视图时,无需调用buttonView.layoutIfNeeded()。仅在设置约束动画时才需要执行此操作,而您在这里没有这样做。添加约束(并解决上述问题)后,按钮将正确布置,而无需明确layoutIfNeeded要求。

  6. 您的视图控制器有一行代码,内容为:

    buttonView.arcCenter = buttonView.center
    
    Run Code Online (Sandbox Code Playgroud)

    那就是混合arcCenter(这是buttonView的坐标空间内的坐标)和buttonView.center(这是视图控制器的根视图的坐标空间按钮中心的坐标)。一个与另一个无关。就个人而言,我会摆脱这种手动设置的arcCenter,而是必须layoutSubviewsButtonView打理这个动态,使用bounds.midXbounds.midY

综合所有这些,您将得到类似以下内容的信息:

@IBDesignable
class CircleButton: UIView {

    private let trackLayer = CAShapeLayer()

    @IBInspectable var lineWidth:   CGFloat = 5          { didSet { updatePath() } }
    @IBInspectable var fillColor:   UIColor = .clear     { didSet { trackLayer.fillColor   = fillColor.cgColor } }
    @IBInspectable var strokeColor: UIColor = .lightGray { didSet { trackLayer.strokeColor = strokeColor.cgColor  } }
    @IBInspectable var strokeStart: CGFloat = 0          { didSet { trackLayer.strokeStart = strokeStart } }
    @IBInspectable var strokeEnd:   CGFloat = 1          { didSet { trackLayer.strokeEnd   = strokeEnd } }

    override init(frame: CGRect) {
        super.init(frame: frame)
        configure()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        configure()
    }

    private func configure() {
        trackLayer.fillColor   = fillColor.cgColor
        trackLayer.strokeColor = strokeColor.cgColor
        trackLayer.strokeStart = strokeStart
        trackLayer.strokeEnd   = strokeEnd

        layer.addSublayer(trackLayer)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        updatePath()
    }

    private func updatePath() {
        let arcCenter = CGPoint(x: bounds.midX, y: bounds.midY)
        let radius = (min(bounds.width, bounds.height) - lineWidth) / 2
        trackLayer.lineWidth = lineWidth
        trackLayer.path = UIBezierPath(arcCenter: arcCenter, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true).cgPath

        // There's no need to rotate it if you're drawing a complete circle.
        // But if you're going to transform, set the `frame`, too.

        trackLayer.transform = CATransform3DIdentity
        trackLayer.frame = bounds
        trackLayer.transform = CATransform3DMakeRotation(-.pi / 2, 0, 0, 1)
    }
}
Run Code Online (Sandbox Code Playgroud)

产生:

在此处输入图片说明

或者,你可以调整权在IB设置,你会看到它生效:

在此处输入图片说明

并且确保所有didSet观察者都ButtonView可以更新路径或直接更新某些形状图层的属性之后,视图控制器现在可以更新这些属性,并将它们自动呈现在中ButtonView