英文:
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秒:
我无法弄清楚为什么。据我了解,它应该从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:
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,20
到 260,20
,线宽为 40
:
您可以通过在视图上设置 layer.borderWidth = 1
来轻松查看发生了什么… 它会类似于这样:
所以,这个更改应该解决您的问题。
但是,我建议,而不是保存宽度/高度并需要调用 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
:
You can easily see what's going on by setting layer.borderWidth = 1
on your view ... it will look similar to this:
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()
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论