用于UITextField的CABasicAnimation的笔画动画

时间:2018-05-14 12:59:10

标签: ios swift uitextfield uibezierpath cabasicanimation

我尝试在用户编辑时将动画添加到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


    }
}

我的结果:

My attempt

预期结果:

What I need to see

修改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")
    }
}

}

这就是我现在所得到的:

Updated Result

编辑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()
    }

}

enter image description here

2 个答案:

答案 0 :(得分:2)

您想要的动画非常酷。尽管如此,实施还需要做很多工作。我做了很多核心动画,创建你的整个动画序列可能需要几天时间。

核心动画路径动画的基本规则是起始路径和结束路径必须具有相同数量和类型的控制点。您需要将动画划分为多个片段并单独制作动画。

对于某些部分(形状没有改变,但你在路径中添加/删除像素,就像用笔绘制和/或删除之前绘制的部分一样),你将拥有一条固定的路径,为strokeStartstrokeEnd属性设置动画。

对于动画的其他部分(形状发生变化),您必须仔细构建具有相同数量和类型的控制点以及所需的起始和结束形状以及它们之间的动画的起始和结束路径。 (这可能意味着对于某些动画,您创建了一个开始或结束路径,其中有许多子路径实际上绘制了更简单的形状。)需要花费很多心思去弄清楚如何做到这一点。

第一步是绘制动画图并将其分为几个阶段。

答案 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")
    }

}