//
// DOFavoriteButton.swift
// DOFavoriteButton
//
// Created by Daiki Okumura on 2015/07/09.
// Copyright (c) 2015 Daiki Okumura. All rights reserved.
//
// This software is released under the MIT License.
// http://opensource.org/licenses/mit-license.php
//
import UIKit
@IBDesignable
public class DOFavoriteButton: UIButton {
private var imageShape: CAShapeLayer!
@IBInspectable public var image: UIImage! {
didSet {
createLayers(image: image)
}
}
@IBInspectable public var imageColorOn: UIColor! = UIColor(red: 255/255, green: 172/255, blue: 51/255, alpha: 1.0) {
didSet {
if (isSelected) {
imageShape.fillColor = imageColorOn.cgColor
}
}
}
@IBInspectable public var imageColorOff: UIColor! = UIColor(red: 136/255, green: 153/255, blue: 166/255, alpha: 1.0) {
didSet {
if (!isSelected) {
imageShape.fillColor = imageColorOff.cgColor
}
}
}
private var circleShape: CAShapeLayer!
private var circleMask: CAShapeLayer!
@IBInspectable public var circleColor: UIColor! = UIColor(red: 255/255, green: 172/255, blue: 51/255, alpha: 1.0) {
didSet {
circleShape.fillColor = circleColor.cgColor
}
}
private var lines: [CAShapeLayer]!
@IBInspectable public var lineColor: UIColor! = UIColor(red: 250/255, green: 120/255, blue: 68/255, alpha: 1.0) {
didSet {
for line in lines {
line.strokeColor = lineColor.cgColor
}
}
}
private let circleTransform = CAKeyframeAnimation(keyPath: "transform")
private let circleMaskTransform = CAKeyframeAnimation(keyPath: "transform")
private let lineStrokeStart = CAKeyframeAnimation(keyPath: "strokeStart")
private let lineStrokeEnd = CAKeyframeAnimation(keyPath: "strokeEnd")
private let lineOpacity = CAKeyframeAnimation(keyPath: "opacity")
private let imageTransform = CAKeyframeAnimation(keyPath: "transform")
@IBInspectable public var duration: Double = 1.0 {
didSet {
circleTransform.duration = 0.333 * duration
circleMaskTransform.duration = 0.333 * duration
lineStrokeStart.duration = 0.6 * duration
lineStrokeEnd.duration = 0.6 * duration
lineOpacity.duration = 1.0 * duration
imageTransform.duration = 1.0 * duration
}
}
override public var isSelected : Bool {
didSet {
if (isSelected != oldValue) {
if isSelected {
imageShape.fillColor = imageColorOn.cgColor
} else {
deselect()
}
}
}
}
public convenience init() {
self.init(frame: CGRect.zero)
}
public override convenience init(frame: CGRect) {
self.init(frame: frame, image: UIImage())
}
public init(frame: CGRect, image: UIImage!) {
super.init(frame: frame)
self.image = image
createLayers(image: image)
addTargets()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
createLayers(image: UIImage())
addTargets()
}
private func createLayers(image: UIImage!) {
self.layer.sublayers = nil
let imageFrame = CGRect(x: frame.size.width / 2 - frame.size.width / 4, y: frame.size.height / 2 - frame.size.height / 4, width: frame.size.width / 2, height: frame.size.height / 2)
let imgCenterPoint = CGPoint(x: imageFrame.midX, y: imageFrame.midY)
let lineFrame = CGRect(x: imageFrame.origin.x - imageFrame.width / 4, y: imageFrame.origin.y - imageFrame.height / 4, width: imageFrame.width * 1.5, height: imageFrame.height * 1.5)
circleShape = CAShapeLayer()
circleShape.bounds = imageFrame
circleShape.position = imgCenterPoint
circleShape.path = UIBezierPath(ovalIn: imageFrame).cgPath
circleShape.fillColor = circleColor.cgColor
circleShape.transform = CATransform3DMakeScale(0.0, 0.0, 1.0)
self.layer.addSublayer(circleShape)
circleMask = CAShapeLayer()
circleMask.bounds = imageFrame
circleMask.position = imgCenterPoint
circleMask.fillRule = kCAFillRuleEvenOdd
circleShape.mask = circleMask
let maskPath = UIBezierPath(rect: imageFrame)
maskPath.addArc(withCenter: imgCenterPoint, radius: 0.1, startAngle: CGFloat(0.0), endAngle: CGFloat(M_PI * 2), clockwise: true)
circleMask.path = maskPath.cgPath
lines = []
for i in 0 ..< 5 {
let line = CAShapeLayer()
line.bounds = lineFrame
line.position = imgCenterPoint
line.masksToBounds = true
line.actions = ["strokeStart": NSNull(), "strokeEnd": NSNull()]
line.strokeColor = lineColor.cgColor
line.lineWidth = 1.25
line.miterLimit = 1.25
line.path = {
let path = CGMutablePath()
path.move(to: CGPoint(x: lineFrame.midX, y: lineFrame.midY))
path.addLine(to: CGPoint(x: lineFrame.origin.x + lineFrame.width / 2, y: lineFrame.origin.y))
return path
}()
line.lineCap = kCALineCapRound
line.lineJoin = kCALineJoinRound
line.strokeStart = 0.0
line.strokeEnd = 0.0
line.opacity = 0.0
line.transform = CATransform3DMakeRotation(CGFloat(M_PI) / 5 * (CGFloat(i) * 2 + 1), 0.0, 0.0, 1.0)
self.layer.addSublayer(line)
lines.append(line)
}
imageShape = CAShapeLayer()
imageShape.bounds = imageFrame
imageShape.position = imgCenterPoint
imageShape.path = UIBezierPath(rect: imageFrame).cgPath
imageShape.fillColor = imageColorOff.cgColor
imageShape.actions = ["fillColor": NSNull()]
self.layer.addSublayer(imageShape)
imageShape.mask = CALayer()
imageShape.mask?.contents = image.cgImage
imageShape.mask?.bounds = imageFrame
imageShape.mask?.position = imgCenterPoint
circleTransform.duration = 0.333
circleTransform.values = [
NSValue(caTransform3D: CATransform3DMakeScale(0.0, 0.0, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(0.5, 0.5, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(1.0, 1.0, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(1.2, 1.2, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(1.3, 1.3, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(1.37, 1.37, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(1.4, 1.4, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(1.4, 1.4, 1.0))
]
circleTransform.keyTimes = [
0.0,
0.1,
0.2,
0.3,
0.4,
0.5,
0.6,
1.0
]
circleMaskTransform.duration = 0.333
circleMaskTransform.values = [
NSValue(caTransform3D: CATransform3DIdentity),
NSValue(caTransform3D: CATransform3DIdentity),
NSValue(caTransform3D: CATransform3DMakeScale(imageFrame.width * 1.25, imageFrame.height * 1.25, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(imageFrame.width * 2.688, imageFrame.height * 2.688, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(imageFrame.width * 3.923, imageFrame.height * 3.923, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(imageFrame.width * 4.375, imageFrame.height * 4.375, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(imageFrame.width * 4.731, imageFrame.height * 4.731, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(imageFrame.width * 5.0, imageFrame.height * 5.0, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(imageFrame.width * 5.0, imageFrame.height * 5.0, 1.0))
]
circleMaskTransform.keyTimes = [
0.0,
0.2,
0.3,
0.4,
0.5,
0.6,
0.7,
0.9,
1.0
]
lineStrokeStart.duration = 0.6
lineStrokeStart.values = [
0.0,
0.0,
0.18,
0.2,
0.26,
0.32,
0.4,
0.6,
0.71,
0.89,
0.92
]
lineStrokeStart.keyTimes = [
0.0,
0.056,
0.111,
0.167,
0.222,
0.278,
0.333,
0.389,
0.444,
0.944,
1.0,
]
lineStrokeEnd.duration = 0.6
lineStrokeEnd.values = [
0.0,
0.0,
0.32,
0.48,
0.64,
0.68,
0.92,
0.92
]
lineStrokeEnd.keyTimes = [
0.0,
0.056,
0.111,
0.167,
0.222,
0.278,
0.944,
1.0,
]
lineOpacity.duration = 1.0
lineOpacity.values = [
1.0,
1.0,
0.0
]
lineOpacity.keyTimes = [
0.0,
0.4,
0.567
]
imageTransform.duration = 1.0
imageTransform.values = [
NSValue(caTransform3D: CATransform3DMakeScale(0.0, 0.0, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(0.0, 0.0, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(1.2, 1.2, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(1.25, 1.25, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(1.2, 1.2, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(0.9, 0.9, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(0.875, 0.875, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(0.875, 0.875, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(0.9, 0.9, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(1.013, 1.013, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(1.025, 1.025, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(1.013, 1.013, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(0.96, 0.96, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(0.95, 0.95, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(0.96, 0.96, 1.0)),
NSValue(caTransform3D: CATransform3DMakeScale(0.99, 0.99, 1.0)),
NSValue(caTransform3D: CATransform3DIdentity)
]
imageTransform.keyTimes = [
0.0,
0.1,
0.3,
0.333,
0.367,
0.467,
0.5,
0.533,
0.567,
0.667,
0.7,
0.733,
0.833,
0.867,
0.9,
0.967,
1.0
]
}
private func addTargets() {
self.addTarget(self, action: #selector(touchDown), for: UIControlEvents.touchDown)
self.addTarget(self, action: #selector(touchUpInside), for: UIControlEvents.touchUpInside)
self.addTarget(self, action: #selector(touchDragExit), for: UIControlEvents.touchDragExit)
self.addTarget(self, action: #selector(touchDragEnter), for: UIControlEvents.touchDragEnter)
self.addTarget(self, action: #selector(touchCancel), for: UIControlEvents.touchCancel)
}
@objc func touchDown(sender: DOFavoriteButton) {
self.layer.opacity = 0.4
}
@objc func touchUpInside(sender: DOFavoriteButton) {
self.layer.opacity = 1.0
}
@objc func touchDragExit(sender: DOFavoriteButton) {
self.layer.opacity = 1.0
}
@objc func touchDragEnter(sender: DOFavoriteButton) {
self.layer.opacity = 0.4
}
@objc func touchCancel(sender: DOFavoriteButton) {
self.layer.opacity = 1.0
}
public func select() {
isSelected = true
imageShape.fillColor = imageColorOn.cgColor
CATransaction.begin()
circleShape.add(circleTransform, forKey: "transform")
circleMask.add(circleMaskTransform, forKey: "transform")
imageShape.add(imageTransform, forKey: "transform")
for i in 0 ..< 5 {
lines[i].add(lineStrokeStart, forKey: "strokeStart")
lines[i].add(lineStrokeEnd, forKey: "strokeEnd")
lines[i].add(lineOpacity, forKey: "opacity")
}
CATransaction.commit()
}
public func deselect() {
isSelected = false
imageShape.fillColor = imageColorOff.cgColor
circleShape.removeAllAnimations()
circleMask.removeAllAnimations()
imageShape.removeAllAnimations()
lines[0].removeAllAnimations()
lines[1].removeAllAnimations()
lines[2].removeAllAnimations()
lines[3].removeAllAnimations()
lines[4].removeAllAnimations()
}
}