読者です 読者をやめる 読者になる 読者になる

CGPath の変化をアニメーションさせるサンプル

環境: Xcode8.2.1, Swift3

CGPath の変化をアニメーションさせる方法を試した。
気をつける点としては変更前と変更後のパスの数を同じにしておくこと。

f:id:xyk:20170310125452g:plain

import UIKit
import PlaygroundSupport

class SquareButton: UIControl {
    
    let pathLayer = CAShapeLayer()
    var squarePath: UIBezierPath!
    var halfMoonPath: UIBezierPath!
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        self.squarePath = self.squarePath(rect: frame)
        self.halfMoonPath = self.halfMoonPath(rect: frame)
        
        self.pathLayer.fillColor = UIColor.orange.cgColor
        self.pathLayer.strokeColor = UIColor.white.cgColor
        self.pathLayer.lineWidth = 4
        self.pathLayer.path = self.squarePath.cgPath
        self.layer.addSublayer(self.pathLayer)
    }
    
    override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
        super.endTracking(touch, with: event)
        
        self.toggle()
    }
    
    func toggle() {
        
        let anim = CABasicAnimation(keyPath: "path")
        
        if self.isSelected {
            anim.fromValue = self.halfMoonPath.cgPath
            anim.toValue = self.squarePath.cgPath
        } else {
            anim.fromValue = self.squarePath.cgPath
            anim.toValue = self.halfMoonPath.cgPath
        }
        
        anim.duration = 0.4
        anim.fillMode = kCAFillModeForwards
        anim.isRemovedOnCompletion = false
        
        self.pathLayer.add(anim, forKey: "animatePath")
        
        self.isSelected = !self.isSelected
    }
    
    func squarePath(rect: CGRect) -> UIBezierPath {
        
        let initialPoint = CGPoint(x: 0, y: 0)
        let curveStart = CGPoint(x: rect.maxX * 0.05, y: 0)
        let curveControl = CGPoint(x: rect.maxX * 0.5, y: 0)
        let curveEnd = CGPoint(x: rect.maxX * 0.95, y: 0)
        let firstCorner = CGPoint(x: rect.maxX, y: 0)
        let secondCorner = CGPoint(x: rect.maxX, y: rect.maxY)
        let thirdCorner = CGPoint(x: 0, y: rect.maxY)
        
        let myBezier = UIBezierPath()
        myBezier.move(to: initialPoint)
        myBezier.addLine(to: curveStart)
        myBezier.addQuadCurve(to: curveEnd, controlPoint: curveControl)
        myBezier.addLine(to: firstCorner)
        myBezier.addLine(to: secondCorner)
        myBezier.addLine(to: thirdCorner)
        
        myBezier.close()
        return myBezier
    }
    
    func halfMoonPath(rect: CGRect) -> UIBezierPath {
        
        let initialPoint = CGPoint(x: 0, y: 0)
        let curveStart = CGPoint(x: rect.maxX * 0.05, y: 0)
        let curveControl = CGPoint(x: rect.maxX * 0.5, y: rect.maxY * 0.6)
        let curveEnd = CGPoint(x: rect.maxX * 0.95, y: 0)
        let firstCorner = CGPoint(x: rect.maxX, y: 0)
        let secondCorner = CGPoint(x: rect.maxX, y: rect.maxY)
        let thirdCorner = CGPoint(x: 0, y: rect.maxY)
        
        let myBezier = UIBezierPath()
        myBezier.move(to: initialPoint)
        myBezier.addLine(to: curveStart)
        myBezier.addQuadCurve(to: curveEnd, controlPoint: curveControl)
        myBezier.addLine(to: firstCorner)
        myBezier.addLine(to: secondCorner)
        myBezier.addLine(to: thirdCorner)
        
        myBezier.close()
        return myBezier
    }

}

let baseView = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 200))
baseView.backgroundColor = UIColor(red: 38.0/255, green: 151.0/255, blue: 68.0/255, alpha: 1)

let button = SquareButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
baseView.addSubview(button)
button.center = baseView.center

PlaygroundPage.current.liveView = baseView