[英]Stroke Animation with CABasicAnimation for UITextField
我正在嘗試在用戶編輯時將動畫添加到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()
}
}
你想要的動畫非常酷。 盡管如此,實施還需要做很多工作。 我做了很多核心動畫,創建你的整個動畫序列可能需要幾天時間。
核心動畫路徑動畫的基本規則是起始路徑和結束路徑必須具有相同數量和類型的控制點。 您需要將動畫分成多個片段並單獨制作動畫。
對於某些部分(形狀沒有改變,但你在路徑中添加/刪除像素,就像你用筆繪制和/或刪除之前繪制的部分一樣),你將擁有一條固定的路徑並為strokeStart
設置動畫和strokeEnd
屬性。
對於動畫的其他部分(形狀發生變化),您必須仔細構建起始路徑和結束路徑,這些路徑具有相同數量和類型的控制點以及所需的起始和結束形狀以及它們之間的動畫。 (這可能意味着對於某些動畫,您創建了一個開始或結束路徑,其中有許多子路徑實際上繪制了更簡單的形狀。)需要花費很多心思去弄清楚如何做到這一點。
第一步是繪制動畫圖並將其分為幾個階段。
這不是一個簡單的事情,可以解釋為StackOverflow答案。
但是,我仍然會告訴你如何實現它。
你應該先做一個BezierPath:
func makePath(){
var path = UIBezierPath()
path.move(to: CGPoint.init(x: self.usernameField.frame.minX, y: self.usernameField.frame.maxY))
path.addLine(to: CGPoint.init(x: self.usernameField.frame.maxX, y: self.usernameField.frame.maxY))
path.addQuadCurve(to: CGPoint.init(x: self.passwordField.frame.maxX, y: self.passwordField.frame.maxY), controlPoint: CGPoint.init(x: self.usernameField.frame.maxX + 10, y: self.usernameField.frame.maxY + 10))
path.addLine(to: CGPoint.init(x: self.passwordField.frame.minX, y: self.passwordField.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
}
在TextFieldDelegate覆蓋中添加動畫:
extension CustomLoginAnimmationController: UITextFieldDelegate{
func textFieldDidBeginEditing(_ textField: UITextField) {
// All the 0.5 for strokeEnd and strokeStart means 50%, You will have to calculate yourself, what percentage value you must add here
if textField == usernameField{
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 {
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")
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.