Swift UIBezierPath 起始偏移位置不正确

huangapple go评论64阅读模式
英文:

Swift UIBezierPath starting offset from where it should

问题

我有一个按钮,当按下时会隐藏,而代替显示一个从左到右填充的动画,表示等待时间。

我有以下处理动画的类:

//MARK: - 类: LinearProgressBarButtonView
class LinearProgressBarButtonView: UIView {
    private var myWidth: CGFloat!
    private var myHeight: CGFloat!
    
    init(frame: CGRect, width: CGFloat, height: CGFloat) {
        super.init(frame: frame)
        myWidth = width
        myHeight = height
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
        
    private var lineLayer = CAShapeLayer()
    private var progressLayer = CAShapeLayer()
    
    func createLinePath() {
        // 为lineLayer和progressLayer创建线路路径
        let rect = CGRect(x: 0, y: 0, width: myWidth, height: myHeight)
        let linePath = UIBezierPath()
        
        linePath.move(to: CGPoint(x: 0.0, y: rect.height/2))
        linePath.addLine(to: CGPoint(x: myWidth, y: rect.height/2))
        
        // 将lineLayer的路径设置为线性路径
        lineLayer.path = linePath.cgPath
        // UI编辑
        lineLayer.fillColor = UIColor.clear.cgColor
        lineLayer.lineCap = .square
        lineLayer.lineWidth = myHeight
        lineLayer.strokeEnd = 1.0
        lineLayer.strokeColor = colors.Blue.cgColor // 请注意,colors.Blue 可能是你自定义的颜色
        // 将circleLayer添加到图层
        layer.addSublayer(lineLayer)
        
        // 将progressLayer的路径设置为线性路径
        progressLayer.path = linePath.cgPath
        // UI编辑
        progressLayer.fillColor = UIColor.clear.cgColor
        progressLayer.lineCap = .square
        progressLayer.lineWidth = myHeight
        progressLayer.strokeEnd = 0
        progressLayer.strokeColor = colors.red.cgColor // 请注意,colors.red 可能是你自定义的颜色
        // 将progressLayer添加到图层
        layer.addSublayer(progressLayer)
    }
    
    func progressAnimation(duration: TimeInterval) {
        // 创建strokeEnd的基本动画
        let linearProgressAnimation = CABasicAnimation(keyPath: "strokeEnd")
        // 设置持续时间
        linearProgressAnimation.duration = duration
        linearProgressAnimation.toValue = 1.0
        linearProgressAnimation.fillMode = .forwards
        linearProgressAnimation.isRemovedOnCompletion = true
        progressLayer.add(linearProgressAnimation, forKey: "progressAnim")
    }
    
    func endAnimation(){
        progressLayer.removeAllAnimations()
    }
}

我已经设置了所有必要的约束,都是正确的。使用以下代码可以创建视图并稍后将其添加为子视图:

linearProgressBarButtonView = LinearProgressBarButtonView(frame: .zero, width: bookButton.frame.width, height: bookButton.frame.height)
linearProgressBarButtonView.createLinePath()

现在,我可以使用linearProgressBarButtonView.progressAnimation(duration: linearViewDuration)来启动动画,它正常运行。然而,动画似乎不是从x = 0开始的,而是在路程中的某个位置(大约在15%左右)。这是动画的第一秒的屏幕截图,它应该持续60秒:

Swift UIBezierPath 起始偏移位置不正确

我无法弄清楚为什么。据我了解,它应该从x = 0开始。宽度应该与视图的宽度完全相同,我在生成动画视图时传递了这个宽度。为什么它会带有偏移呢?

英文:

I have a Button that hides when pressed and instead an animation is shown that fills from left to right, indicating a wait time.

I have the following class which handles the animation:

//MARK: - Class: LinearProgressBarButtonView
class LinearProgressBarButtonView: UIView {
private var myWidth: CGFloat!
private var myHeight: CGFloat!

init(frame: CGRect, width: CGFloat, height: CGFloat) {
    super.init(frame: frame)
    myWidth = width
    myHeight = height
}

required init?(coder: NSCoder) {
    super.init(coder: coder)
}
    
private var lineLayer = CAShapeLayer()
private var progressLayer = CAShapeLayer()

func createLinePath() {
    // created linePath for lineLayer and progressLayer
    let rect = CGRect(x: 0, y: 0, width: myWidth, height: myHeight)
    let linePath = UIBezierPath()
    
    linePath.move(to: CGPoint(x: 0.0, y: rect.height/2))
    linePath.addLine(to: CGPoint(x: myWidth, y: rect.height/2))
    
    // lineLayer path defined to circularPath
    lineLayer.path = linePath.cgPath
    // ui edits
    lineLayer.fillColor = UIColor.clear.cgColor
    lineLayer.lineCap = .square
    lineLayer.lineWidth = myHeight
    lineLayer.strokeEnd = 1.0
    lineLayer.strokeColor = colors.Blue.cgColor
    // added circleLayer to layer
    layer.addSublayer(lineLayer)
    
    // progressLayer path defined to circularPath
    progressLayer.path = linePath.cgPath
    // ui edits
    progressLayer.fillColor = UIColor.clear.cgColor
    progressLayer.lineCap = .square
    progressLayer.lineWidth = myHeight
    progressLayer.strokeEnd = 0
    progressLayer.strokeColor = colors.red.cgColor
    // added progressLayer to layer
    layer.addSublayer(progressLayer)
}

func progressAnimation(duration: TimeInterval) {
    // created circularProgressAnimation with keyPath
    let linearProgressAnimation = CABasicAnimation(keyPath: "strokeEnd")
    // set the end time
    linearProgressAnimation.duration = duration
    linearProgressAnimation.toValue = 1.0
    linearProgressAnimation.fillMode = .forwards
    linearProgressAnimation.isRemovedOnCompletion = true
    progressLayer.add(linearProgressAnimation, forKey: "progressAnim")
}

func endAnimation(){
    progressLayer.removeAllAnimations()
}
}

I have set all necessary constraints, which are all correct. Using

linearProgressBarButtonView = LinearProgressBarButtonView(frame: .zero, width: bookButton.frame.width, height: bookButton.frame.height)
linearProgressBarButtonView.createLinePath()

I can create the view and later add it as a subview.

I can now use linearProgressBarButtonView.progressAnimation(duration: linearViewDuration) to start the animation, which works exactly as it should. However, the animation does not seem to start at x = 0, but further along the way (somewhere at around 15%). Here is a screenshot of the first second of the animation, which is supposed to last 60 seconds:

Swift UIBezierPath 起始偏移位置不正确

I can't seem to figure out why. As far as I understand, it should start from x = 0. And the width should be the exact same width as the view has, which I pass when generating the animated view. Why is it starting with an offset then?

答案1

得分: 2

主要问题是:

lineLayer.lineCap = .square
// 和
progressLayer.lineCap = .square

这些需要改为 .butt

当设置为 .square 时,线宽的一半将会“添加”在每一端。在这里,线路从 0,20260,20,线宽为 40

Swift UIBezierPath 起始偏移位置不正确

您可以通过在视图上设置 layer.borderWidth = 1 来轻松查看发生了什么… 它会类似于这样:

Swift UIBezierPath 起始偏移位置不正确

所以,这个更改应该解决您的问题。

但是,我建议,而不是保存宽度/高度并需要调用 createLinePath(),将该代码移至 layoutSubviews()。这将始终保持您的线路径正确,即使您在以后更改框架:

class LinearProgressBarButtonView: UIView {
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    private func commonInit() {
        layer.addSublayer(lineLayer)
        layer.addSublayer(progressLayer)
    }
    
    private var lineLayer = CAShapeLayer()
    private var progressLayer = CAShapeLayer()

    override func layoutSubviews() {
        super.layoutSubviews()

        let linePath = UIBezierPath()
        
        linePath.move(to: CGPoint(x: bounds.minX, y: bounds.midY))
        linePath.addLine(to: CGPoint(x: bounds.maxX, y: bounds.midY))

        lineLayer.path = linePath.cgPath
        // UI编辑
        lineLayer.fillColor = UIColor.clear.cgColor
        lineLayer.lineCap = .butt
        lineLayer.lineWidth = bounds.height
        lineLayer.strokeEnd = 1.0
        lineLayer.strokeColor = UIColor.systemBlue.cgColor

        progressLayer.path = linePath.cgPath
        // UI编辑
        progressLayer.fillColor = UIColor.clear.cgColor
        progressLayer.lineCap = .butt
        progressLayer.lineWidth = bounds.height
        progressLayer.strokeEnd = 0.0
        progressLayer.strokeColor = UIColor.systemRed.cgColor
    }
    
    func progressAnimation(duration: TimeInterval) {
        
        // 创建了具有 keyPath 的 ProgressAnimation
        let linearProgressAnimation = CABasicAnimation(keyPath: "strokeEnd")
        // 设置结束时间
        linearProgressAnimation.duration = duration
        linearProgressAnimation.fromValue = 0.0
        linearProgressAnimation.toValue = 1.0
        linearProgressAnimation.fillMode = .forwards
        linearProgressAnimation.isRemovedOnCompletion = true
        progressLayer.add(linearProgressAnimation, forKey: "progressAnim")
        
    }
    
    func endAnimation(){
        progressLayer.removeAllAnimations()
    }
}
英文:

The main problem is:

lineLayer.lineCap = .square
// and
progressLayer.lineCap = .square

Those need to be .butt

When set to .square one-half the line-width will be "added" on each end. Here, the line path goes from 0,20 to 260,20, with a .lineWidth = 40:

Swift UIBezierPath 起始偏移位置不正确

You can easily see what's going on by setting layer.borderWidth = 1 on your view ... it will look similar to this:

Swift UIBezierPath 起始偏移位置不正确

So, that change should fix your issue.

However, I'd suggest -- instead of saving width/height and having to call createLinePath(), move that code into layoutSubviews(). That will always keep your line path correct, even if you change the frame at a later point:

class LinearProgressBarButtonView: UIView {
	
	override init(frame: CGRect) {
		super.init(frame: frame)
		commonInit()
	}
	required init?(coder: NSCoder) {
		super.init(coder: coder)
		commonInit()
	}
	private func commonInit() {
		layer.addSublayer(lineLayer)
		layer.addSublayer(progressLayer)
	}
	
	private var lineLayer = CAShapeLayer()
	private var progressLayer = CAShapeLayer()

	override func layoutSubviews() {
		super.layoutSubviews()

		let linePath = UIBezierPath()
		
		linePath.move(to: CGPoint(x: bounds.minX, y: bounds.midY))
		linePath.addLine(to: CGPoint(x: bounds.maxX, y: bounds.midY))

		lineLayer.path = linePath.cgPath
		// ui edits
		lineLayer.fillColor = UIColor.clear.cgColor
		lineLayer.lineCap = .butt
		lineLayer.lineWidth = bounds.height
		lineLayer.strokeEnd = 1.0
		lineLayer.strokeColor = UIColor.systemBlue.cgColor

		progressLayer.path = linePath.cgPath
		// ui edits
		progressLayer.fillColor = UIColor.clear.cgColor
		progressLayer.lineCap = .butt
		progressLayer.lineWidth = bounds.height
		progressLayer.strokeEnd = 0.0
		progressLayer.strokeColor = UIColor.systemRed.cgColor
	}
	
	func progressAnimation(duration: TimeInterval) {
		
		// created ProgressAnimation with keyPath
		let linearProgressAnimation = CABasicAnimation(keyPath: "strokeEnd")
		// set the end time
		linearProgressAnimation.duration = duration
		linearProgressAnimation.fromValue = 0.0
		linearProgressAnimation.toValue = 1.0
		linearProgressAnimation.fillMode = .forwards
		linearProgressAnimation.isRemovedOnCompletion = true
		progressLayer.add(linearProgressAnimation, forKey: "progressAnim")
		
	}
	
	func endAnimation(){
		progressLayer.removeAllAnimations()
	}
}

huangapple
  • 本文由 发表于 2023年2月14日 20:28:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/75447844.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定