我尝试在用户编辑时将动画添加到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()
}
}
答案 0 :(得分:2)
您想要的动画非常酷。尽管如此,实施还需要做很多工作。我做了很多核心动画,创建你的整个动画序列可能需要几天时间。
核心动画路径动画的基本规则是起始路径和结束路径必须具有相同数量和类型的控制点。您需要将动画划分为多个片段并单独制作动画。
对于某些部分(形状没有改变,但你在路径中添加/删除像素,就像用笔绘制和/或删除之前绘制的部分一样),你将拥有一条固定的路径,为strokeStart
和strokeEnd
属性设置动画。
对于动画的其他部分(形状发生变化),您必须仔细构建具有相同数量和类型的控制点以及所需的起始和结束形状以及它们之间的动画的起始和结束路径。 (这可能意味着对于某些动画,您创建了一个开始或结束路径,其中有许多子路径实际上绘制了更简单的形状。)需要花费很多心思去弄清楚如何做到这一点。
第一步是绘制动画图并将其分为几个阶段。
答案 1 :(得分:2)
这不是一个简单的事情,可以解释为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")
}
}