Проблема в том, что ваш путь представляет собой скругленный прямоугольник. На изображении, которым вы поделились с нами, это, вероятно, около 2-3% инсульта Измените его на 90% пути, и вы увидите, что он пытается нарисовать широкий и очень короткий прямоугольник с закругленными углами, например ::10000
Вместо этого просто сделайте путь линией, и она будет работать как положено:
let path = UIBezierPath()
let bounds = topView.bounds
path.move(to: CGPoint(x: bounds.minX + bounds.width * 0.15, y: bounds.minY + bounds.height / 1.5))
path.addLine(to: CGPoint(x: bounds.minX + bounds.width * 0.85, y: bounds.minY + bounds.height / 1.5))
Возможно, вы также захотите скруглить шапки слоев вашей фигуры:
trackLayer.lineCap = .round // or whatever you want
shapeLayer.lineCap = .round
И, конечно, это изменение потеряло высоту в 2 пункта вашего первоначального пути, поэтому, если вы хотите сделать эти слои слоя более толстыми, просто увеличьте их соответствующие значения lineWidth
.
Пара несвязанных наблюдений:
viewDidLayoutSubviews()
и viewDidAppear(_:)
должны вызывать их super
реализации.
viewDidLayoutSubviews()
можно вызывать несколько раз, поэтому вы не хотите каждый раз создавать новый trackLayer
. Или, если вы это сделаете, обязательно удалите предыдущую.
При добавлении подпредставлений / подслоев целесообразно использовать bounds
вместо frame
. В этом случае это, вероятно, не имеет значения, но в некоторых случаях вы можете получить всевозможные странные проблемы, потому что frame
находится в системе координат суперпредставления представления, тогда как bounds
- система координат рассматриваемого представления.
Лично, если бы вы сохранили этот код в контроллере представления, я бы предложил:
- добавить слои формы и градиент в
viewDidLoad
;
- обновить пути и границы градиента с
viewDidLayoutSubviews
;
- Я бы поместил эти различные методы создания слоя и градиента в их собственное частное расширение.
Более того, весь этот анимационный код на самом деле вообще не принадлежит контроллеру представления приложения, а является подклассом UIView
(или дочерним контроллером представления).
Таким образом, возможно:
@IBDesignable
public class GradientProgressView: UIView {
private var shapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.green.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineCap = .round
return shapeLayer
}()
private var trackLayer: CAShapeLayer = {
let trackLayer = CAShapeLayer()
trackLayer.strokeColor = UIColor.groupTableViewBackground.cgColor
trackLayer.fillColor = UIColor.clear.cgColor
trackLayer.lineCap = .round
return trackLayer
}()
private var gradient: CAGradientLayer = {
let gradient = CAGradientLayer()
let color = UIColor(red: 11/255, green: 95/255, blue: 244/255, alpha: 1).cgColor
let sndColor = UIColor(red: 255/255, green: 87/255, blue: 87/255, alpha: 1).cgColor
gradient.colors = [color, sndColor]
gradient.locations = [0.0, 1.0]
gradient.startPoint = CGPoint(x: 0, y: 0)
gradient.endPoint = CGPoint(x: 1, y: 0)
return gradient
}()
override init(frame: CGRect = .zero) {
super.init(frame: frame)
addSubLayers()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addSubLayers()
}
override public func layoutSubviews() {
super.layoutSubviews()
updatePaths()
}
override public func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setProgress(0.75, animated: false)
}
public func setProgress(_ progress: CGFloat, animated: Bool = true) {
if animated {
animateStroke(to: progress)
} else {
shapeLayer.strokeEnd = progress
}
}
}
private extension GradientProgressView {
func addSubLayers() {
layer.addSublayer(trackLayer)
layer.addSublayer(shapeLayer)
layer.addSublayer(gradient)
}
func updatePaths() {
let lineWidth = bounds.height / 2
trackLayer.lineWidth = lineWidth * 0.75
shapeLayer.lineWidth = lineWidth
let path = UIBezierPath()
path.move(to: CGPoint(x: bounds.minX + lineWidth / 2, y: bounds.midY))
path.addLine(to: CGPoint(x: bounds.maxX - lineWidth / 2, y: bounds.midY))
trackLayer.path = path.cgPath
shapeLayer.path = path.cgPath
gradient.frame = bounds
gradient.mask = shapeLayer
}
func animateStroke(to progress: CGFloat) {
let key = "lineStrokeAnimation"
layer.removeAnimation(forKey: key)
let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
basicAnimation.toValue = progress
basicAnimation.duration = 1.5
basicAnimation.fillMode = .forwards
basicAnimation.isRemovedOnCompletion = false
basicAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
shapeLayer.add(basicAnimation, forKey: key)
}
}
Тогда контроллер вида просто:
class ViewController: UIViewController {
@IBOutlet weak var gradientProgressView: GradientProgressView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateProgress()
}
...
}
// MARK: - Progress related methods
private extension ViewController {
func updateProgress() {
let distance = currLeasingCar!.currentKm - currLeasing!.startKm
let value = CGFloat(distance) / CGFloat(finalKm)
gradientProgressView.setProgress(value)
}
}