Swift анимиировать tabcontroller
Я сделал кастомный tabcontroller, когда юзер переключает влкадку анимирую переход вкладки, проблема в том, что граница обводки не анимируется, а просто переходит, не подскажите, как анимировать переход границы ?
private func drawCurve() {
let fillColor: UIColor = .white
let strokeColor: UIColor = Colors.borderTabbarGrayColor
tabWidth = self.bounds.width / CGFloat(self.items!.count)
let bezPath = drawPathBorder(for: index) //drawPath(for: index)
bezPath.close()
fillColor.setFill()
bezPath.fill()
bezPath.lineWidth = 1
strokeColor.setStroke()
bezPath.stroke()
let mask = CAShapeLayer()
mask.fillRule = .evenOdd
mask.path = bezPath.cgPath
if (self.animated) {
let bezAnimation = CABasicAnimation(keyPath: "path")
let bezPathFrom = drawPathBorder(for: previousIndex) //drawPath(for: previousIndex)
bezAnimation.toValue = bezPath.cgPath
bezAnimation.fromValue = bezPathFrom.cgPath
bezAnimation.duration = 3.3
bezAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
mask.add(bezAnimation, forKey: nil)
}
self.layer.mask = mask
}
UPD: Метод drawPathBorder
private func drawPathBorder(for index: CGFloat) -> UIBezierPath {
let bezPath = UIBezierPath()
let leftPoint = CGPoint(x: (index * tabWidth), y: 0)
let leftPointCurveUp = CGPoint(
x: ((tabWidth * index) + tabWidth / 5),
y: 0)
let leftPointCurveDown = CGPoint(
x: ((index * tabWidth) - tabWidth*0.2) + tabWidth / 4,
y: tabHeight*0.40)
let middlePoint = CGPoint(
x: (tabWidth * index) + tabWidth / 2,
y: tabHeight*0.4)
let middlePointCurveDown = CGPoint(
x: (((index * tabWidth) - tabWidth*0.2) + tabWidth / 10) + tabWidth,
y: tabHeight*0.40)
let middlePointCurveUp = CGPoint(
x: (((tabWidth * index) + tabWidth) - tabWidth / 5),
y: 0)
let rightPoint = CGPoint(x: (tabWidth * index) + tabWidth, y: 0)
bezPath.move(to: leftPoint)
bezPath.addCurve(to: middlePoint, controlPoint1: leftPointCurveUp, controlPoint2: leftPointCurveDown)
bezPath.addCurve(to: rightPoint, controlPoint1: middlePointCurveDown, controlPoint2: middlePointCurveUp)
let width = UIScreen.main.bounds.width
bezPath.addLine(to: CGPoint(x: width, y: 0))
bezPath.addLine(to: CGPoint(x: width, y: 83))
bezPath.addLine(to: CGPoint(x: 0, y: 83))
bezPath.addLine(to: CGPoint(x: 0, y: 0))
bezPath.addLine(to: leftPoint)
// bezPath.append(UIBezierPath(rect: self.bounds))
return bezPath
}
Ответы (1 шт):
Автор решения: schmidt9
→ Ссылка
На основе вашего кода я написал следующий пример. Суть в том, что свойству bezAnimation.toValue нужно присваивать путь, к которому нужно перейти при анимации
import UIKit
class TabBarView: UIView {
private var currentIndex = 0
private var nextIndex = 0
private var tabsCount: Int {
items.count
}
private var tabWidth: CGFloat {
guard items.count > 0 else {
return 0
}
return bounds.width / CGFloat(items.count)
}
private var tabHeight: CGFloat {
bounds.height
}
private var curveLayer: CAShapeLayer!
var items: [String] = []
var animated = true
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
func setup() {
let recognizer = UITapGestureRecognizer(target: self, action: #selector(tap(_:)))
addGestureRecognizer(recognizer)
}
override func draw(_ rect: CGRect) {
drawCurve(for: currentIndex)
}
private func drawCurve(for index: Int) {
let fillColor: UIColor = .white
let strokeColor: UIColor = .gray
// 1. draw current tab
let currentTabBezierPath = drawPathBorder(for: index)
currentTabBezierPath.close()
if curveLayer == nil {
curveLayer = CAShapeLayer()
curveLayer.fillRule = .evenOdd
curveLayer.frame = bounds
curveLayer.fillColor = fillColor.cgColor
curveLayer.strokeColor = strokeColor.cgColor
curveLayer.lineWidth = 1
layer.addSublayer(curveLayer)
}
curveLayer.path = currentTabBezierPath.cgPath
if currentIndex == nextIndex {
return
}
if (self.animated) {
// 2. animate to next tab
let nextTabBezierPath = drawPathBorder(for: nextIndex)
let bezAnimation = CABasicAnimation(keyPath: "path")
// The next two line preserves the final shape of animation,
// if you remove it the shape will return to the original shape after the animation finished
// see https://stackoverflow.com/a/26848581/3004003
bezAnimation.fillMode = .forwards
bezAnimation.isRemovedOnCompletion = false
bezAnimation.toValue = nextTabBezierPath.cgPath
bezAnimation.duration = 3.3
bezAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
curveLayer.add(bezAnimation, forKey: nil)
}
currentIndex = nextIndex
}
private func drawPathBorder(for index: Int) -> UIBezierPath {
let bezPath = UIBezierPath()
let floatIndex = CGFloat(index)
let leftPoint = CGPoint(x: (floatIndex * tabWidth), y: 0)
let leftPointCurveUp = CGPoint(
x: ((tabWidth * floatIndex) + tabWidth / 5),
y: 0)
// Casting to CGFloat to avoid "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions"
let leftPointCurveDown = CGPoint(
x: ((floatIndex * tabWidth) - tabWidth * CGFloat(0.2)) + tabWidth / CGFloat(4.0),
y: CGFloat(tabHeight * 0.40))
let middlePoint = CGPoint(
x: (tabWidth * floatIndex) + tabWidth / 2,
y: tabHeight * 0.4)
let middlePointCurveDown = CGPoint(
x: (((floatIndex * tabWidth) - tabWidth * CGFloat(0.2)) + tabWidth / CGFloat(10.0)) + tabWidth,
y: tabHeight * 0.40)
let middlePointCurveUp = CGPoint(
x: (((tabWidth * floatIndex) + tabWidth) - tabWidth / 5.0),
y: 0)
let rightPoint = CGPoint(x: (tabWidth * floatIndex) + tabWidth, y: 0)
bezPath.move(to: leftPoint)
bezPath.addCurve(to: middlePoint, controlPoint1: leftPointCurveUp, controlPoint2: leftPointCurveDown)
bezPath.addCurve(to: rightPoint, controlPoint1: middlePointCurveDown, controlPoint2: middlePointCurveUp)
let width = UIScreen.main.bounds.width
bezPath.addLine(to: CGPoint(x: width, y: 0))
bezPath.addLine(to: CGPoint(x: width, y: 83))
bezPath.addLine(to: CGPoint(x: 0, y: 83))
bezPath.addLine(to: CGPoint(x: 0, y: 0))
bezPath.addLine(to: leftPoint)
return bezPath
}
@objc func tap(_ recognizer: UITapGestureRecognizer) {
let location = recognizer.location(in: self)
nextIndex = tabIndex(with: location)
setNeedsDisplay()
}
private func tabIndex(with location:CGPoint) -> Int {
Int(location.x / tabWidth)
}
}

