Я пытаюсь добавить анимацию к границе UITextField
при редактировании пользователем.
Идея состоит в том, чтобы отобразить линейную анимацию на странице входа после редактирования первого текстового поля, а затемпосле того, как пользователь переключится на следующее текстовое поле, строка должна перейти к следующему текстовому полю.
Я предпринял попытку, которая не работает так, как я ожидал.
Мой код:
class ViewController: UIViewController, UITextFieldDelegate {
@IBOutlet weak var verticSpace: NSLayoutConstraint!
@IBOutlet weak var usernameTxtField: UITextField!
@IBOutlet weak var passwordTxtField: UITextField!
weak var shapeLayer: CAShapeLayer?
let path = UIBezierPath()
let shapeLayerNew = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
usernameTxtField.delegate = self
passwordTxtField.delegate = self
}
func textFieldDidBeginEditing(_ textField: UITextField) {
let path = UIBezierPath()
if textField == usernameTxtField {
if textField.text == "" {
self.startMyLine()
}
}
if passwordTxtField.isFirstResponder {
let path2 = UIBezierPath()
path2.move(to: CGPoint(x: usernameTxtField.frame.width, y: usernameTxtField.frame.height - shapeLayerNew.lineWidth))
path2.addLine(to: CGPoint(x: usernameTxtField.frame.width, y: (usernameTxtField.frame.height - shapeLayerNew.lineWidth) + passwordTxtField.frame.height + verticSpace.constant))
path2.addLine(to: CGPoint(x: 0, y: (usernameTxtField.frame.height - shapeLayerNew.lineWidth) + passwordTxtField.frame.height + verticSpace.constant))
let combinedPath = path.cgPath.mutableCopy()
combinedPath?.addPath(path2.cgPath)
shapeLayerNew.path = path2.cgPath
let startAnimation = CABasicAnimation(keyPath: "strokeStart")
startAnimation.fromValue = 0
startAnimation.toValue = 0.8
let endAnimation = CABasicAnimation(keyPath: "strokeEnd")
endAnimation.fromValue = 0.2
endAnimation.toValue = 1.0
let animation = CAAnimationGroup()
animation.animations = [startAnimation, endAnimation]
animation.duration = 2
shapeLayerNew.add(animation, forKey: "MyAnimation")
}
}
func startMyLine() {
self.shapeLayer?.removeFromSuperlayer()
// create whatever path you want
shapeLayerNew.fillColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0).cgColor
shapeLayerNew.strokeColor = #colorLiteral(red: 1, green: 0, blue: 0, alpha: 1).cgColor
shapeLayerNew.lineWidth = 4
path.move(to: CGPoint(x: 0, y: usernameTxtField.frame.height - shapeLayerNew.lineWidth))
path.addLine(to: CGPoint(x: usernameTxtField.frame.width, y: usernameTxtField.frame.height - shapeLayerNew.lineWidth))
// create shape layer for that path
shapeLayerNew.path = path.cgPath
// animate it
usernameTxtField.layer.addSublayer(shapeLayerNew)
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.duration = 2
shapeLayerNew.add(animation, forKey: "MyAnimation")
// save shape layer
self.shapeLayer = shapeLayerNew
}
}
Мой результат:
Ожидаемый результат:
Редактировать 1:
Я применил изменения, основанные на @SWAT ответ, но я все еще не могу получить ожидаемый результат.Когда поле имени пользователя редактируется, я отображаю четырехугольную строку, в то время как она должна появляться только при переходе к следующему текстовому полю, а затем четверная строка должна исчезнуть после завершения анимации.
Мой обновленный код:
class ViewController: UIViewController, UITextFieldDelegate {
@IBOutlet weak var usernameTxtField: UITextField!
@IBOutlet weak var passwordTxtField: UITextField!
weak var shapeLayer: CAShapeLayer?
let path = UIBezierPath()
let shapeLayerNew = CAShapeLayer()
var animLayer = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
usernameTxtField.delegate = self
passwordTxtField.delegate = self
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == usernameTxtField{
var path = UIBezierPath()
path.move(to: CGPoint.init(x: self.usernameTxtField.frame.minX, y: self.usernameTxtField.frame.maxY))
path.addLine(to: CGPoint.init(x: self.usernameTxtField.frame.maxX, y: self.usernameTxtField.frame.maxY))
path.addQuadCurve(to: CGPoint.init(x: self.passwordTxtField.frame.maxX, y: self.passwordTxtField.frame.maxY), controlPoint: CGPoint.init(x: self.usernameTxtField.frame.maxX, y: self.usernameTxtField.frame.maxY))
path.addLine(to: CGPoint.init(x: self.passwordTxtField.frame.minX, y: self.passwordTxtField.frame.maxY))
animLayer.fillColor = UIColor.clear.cgColor
animLayer.path = path.cgPath
animLayer.strokeColor = UIColor.cyan.cgColor
animLayer.lineWidth = 3.0
self.view.layer.addSublayer(animLayer)
animLayer.strokeEnd = 0
animLayer.strokeStart = 0
let initialAnimation = CABasicAnimation(keyPath: "strokeEnd")
initialAnimation.toValue = 0.5
initialAnimation.beginTime = 0
initialAnimation.duration = 0.5
initialAnimation.fillMode = kCAFillModeBoth
initialAnimation.isRemovedOnCompletion = false
animLayer.add(initialAnimation, forKey: "usernameFieldStrokeEnd")
let secondTextFieldAnimStrokeStart = CABasicAnimation(keyPath: "strokeStart")
secondTextFieldAnimStrokeStart.toValue = 0
secondTextFieldAnimStrokeStart.beginTime = 0
secondTextFieldAnimStrokeStart.duration = 0.5
secondTextFieldAnimStrokeStart.fillMode = kCAFillModeBoth
secondTextFieldAnimStrokeStart.isRemovedOnCompletion = false
animLayer.add(secondTextFieldAnimStrokeStart, forKey: "usernameFieldStrokeStart")
} else {
var path = UIBezierPath()
path.move(to: CGPoint.init(x: self.usernameTxtField.frame.minX, y: self.usernameTxtField.frame.maxY))
path.addLine(to: CGPoint.init(x: self.usernameTxtField.frame.maxX, y: self.usernameTxtField.frame.maxY))
path.addQuadCurve(to: CGPoint.init(x: self.passwordTxtField.frame.maxX, y: self.passwordTxtField.frame.maxY), controlPoint: CGPoint.init(x: self.usernameTxtField.frame.maxX, y: self.usernameTxtField.frame.maxY))
path.addLine(to: CGPoint.init(x: self.passwordTxtField.frame.minX, y: self.passwordTxtField.frame.maxY))
animLayer.fillColor = UIColor.clear.cgColor
animLayer.path = path.cgPath
animLayer.strokeColor = UIColor.cyan.cgColor
animLayer.lineWidth = 3.0
self.view.layer.addSublayer(animLayer)
animLayer.strokeEnd = 0
animLayer.strokeStart = 0
let secondTextFieldAnimStrokeEnd = CABasicAnimation(keyPath: "strokeEnd")
secondTextFieldAnimStrokeEnd.toValue = 1.0
secondTextFieldAnimStrokeEnd.beginTime = 0
secondTextFieldAnimStrokeEnd.duration = 0.5
secondTextFieldAnimStrokeEnd.fillMode = kCAFillModeBoth
secondTextFieldAnimStrokeEnd.isRemovedOnCompletion = false
animLayer.add(secondTextFieldAnimStrokeEnd, forKey: "secondTextFieldStrokeEnd")
let secondTextFieldAnimStrokeStart = CABasicAnimation(keyPath: "strokeStart")
secondTextFieldAnimStrokeStart.toValue = 0.5
secondTextFieldAnimStrokeStart.beginTime = 0
secondTextFieldAnimStrokeStart.duration = 0.5
secondTextFieldAnimStrokeStart.fillMode = kCAFillModeBoth
secondTextFieldAnimStrokeStart.isRemovedOnCompletion = false
animLayer.add(secondTextFieldAnimStrokeStart, forKey: "secondTextFieldStrokeStart")
}
}
}
Вот что я получаю сейчас:
Редактировать2:
Мне удалось найти способ, который дал мне довольно близкий результат к тому, что я ожидаю.Я установил для isRemoveCompletion значение true, чтобы удалить строку после завершения анимации, а затем добавить нижнюю границу к текстовому полю.
class ViewController: UIViewController, UITextFieldDelegate {
@IBOutlet weak var usernameTxtField: UITextField!
@IBOutlet weak var passwordTxtField: UITextField!
var animLayer = CAShapeLayer()
let newLayer2 = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
usernameTxtField.delegate = self
passwordTxtField.delegate = self
makePath()
}
func makePath(){
//var path = UIBezierPath()
let path = UIBezierPath()
path.move(to: CGPoint.init(x: self.usernameTxtField.frame.minX, y: self.usernameTxtField.frame.maxY))
path.addLine(to: CGPoint.init(x: self.usernameTxtField.frame.maxX, y: self.usernameTxtField.frame.maxY))
path.addQuadCurve(to: CGPoint.init(x: self.passwordTxtField.frame.maxX, y: self.passwordTxtField.frame.maxY), controlPoint: CGPoint.init(x: self.usernameTxtField.frame.maxX, y: self.usernameTxtField.frame.maxY))
path.addLine(to: CGPoint.init(x: self.passwordTxtField.frame.minX, y: self.passwordTxtField.frame.maxY))
animLayer.fillColor = UIColor.clear.cgColor
animLayer.path = path.cgPath
animLayer.strokeColor = UIColor(red: 214/255, green: 54/255, blue: 57/255, alpha: 1).cgColor
animLayer.lineWidth = 3.0
animLayer.lineCap = kCALineCapRound
animLayer.lineJoin = kCALineJoinRound
self.view.layer.addSublayer(animLayer)
animLayer.strokeEnd = 0
animLayer.strokeStart = 0
}
func addBottomBorder(textField: UITextField) {
var path = UIBezierPath()
path.move(to: CGPoint.init(x: textField.frame.minX, y: textField.frame.maxY))
path.addLine(to: CGPoint.init(x: textField.frame.maxX, y: textField.frame.maxY))
self.newLayer2.fillColor = UIColor.clear.cgColor
self.newLayer2.path = path.cgPath
self.newLayer2.strokeColor = UIColor(red: 214/255, green: 54/255, blue: 57/255, alpha: 1).cgColor
self.newLayer2.lineWidth = 3.0
self.newLayer2.lineCap = kCALineCapRound
self.newLayer2.lineJoin = kCALineJoinRound
self.view.layer.addSublayer(self.newLayer2)
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == usernameTxtField{
CATransaction.begin()
self.newLayer2.removeFromSuperlayer()
let initialAnimation = CABasicAnimation(keyPath: "strokeEnd")
initialAnimation.toValue = 0.45
initialAnimation.beginTime = 0
initialAnimation.duration = 1.0
initialAnimation.fillMode = kCAFillModeBoth
initialAnimation.isRemovedOnCompletion = true
initialAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animLayer.add(initialAnimation, forKey: "usernameFieldStrokeEnd")
let secondTextFieldAnimStrokeStart = CABasicAnimation(keyPath: "strokeStart")
secondTextFieldAnimStrokeStart.toValue = 0
secondTextFieldAnimStrokeStart.beginTime = 0
secondTextFieldAnimStrokeStart.duration = 1.0
secondTextFieldAnimStrokeStart.fillMode = kCAFillModeBoth
secondTextFieldAnimStrokeStart.isRemovedOnCompletion = true
secondTextFieldAnimStrokeStart.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
CATransaction.setCompletionBlock {
if !self.passwordTxtField.isFirstResponder {
self.addBottomBorder(textField: self.usernameTxtField)
}
}
animLayer.add(secondTextFieldAnimStrokeStart, forKey: "usernameFieldStrokeStart")
CATransaction.commit()
} else {
CATransaction.begin()
self.newLayer2.removeFromSuperlayer()
let secondTextFieldAnimStrokeEnd = CABasicAnimation(keyPath: "strokeEnd")
secondTextFieldAnimStrokeEnd.toValue = 1.0
secondTextFieldAnimStrokeEnd.beginTime = 0
secondTextFieldAnimStrokeEnd.duration = 1.0
secondTextFieldAnimStrokeEnd.fillMode = kCAFillModeBoth
secondTextFieldAnimStrokeEnd.isRemovedOnCompletion = true
secondTextFieldAnimStrokeEnd.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animLayer.add(secondTextFieldAnimStrokeEnd, forKey: "secondTextFieldStrokeEnd")
let secondTextFieldAnimStrokeStart = CABasicAnimation(keyPath: "strokeStart")
secondTextFieldAnimStrokeStart.toValue = 0.5
secondTextFieldAnimStrokeStart.beginTime = 0
secondTextFieldAnimStrokeStart.duration = 1.0
secondTextFieldAnimStrokeStart.fillMode = kCAFillModeBoth
secondTextFieldAnimStrokeStart.isRemovedOnCompletion = true
secondTextFieldAnimStrokeStart.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
CATransaction.setCompletionBlock {
if !self.usernameTxtField.isFirstResponder {
self.addBottomBorder(textField: self.passwordTxtField)
}
}
animLayer.add(secondTextFieldAnimStrokeStart, forKey: "secondTextFieldStrokeStart")
CATransaction.commit()
}
}