Created
March 28, 2020 12:34
-
-
Save xmollv/8b0f725486c293c3b50161f60d81fc70 to your computer and use it in GitHub Desktop.
Playground for an animated checkmark/cross view
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import PlaygroundSupport | |
class AnimatedResultView: UIView { | |
enum Result { | |
case success | |
case failure | |
} | |
//MARK: Public properties | |
var lineColor: UIColor = .white { | |
didSet { | |
self.shapeLayer.strokeColor = self.lineColor.cgColor | |
} | |
} | |
//MARK: Private properties | |
private let shapeLayer = CAShapeLayer() | |
private let tickViewSize: CGFloat | |
//MARK: Lifecycle | |
public init(tickViewSize: CGFloat = 80.0) { | |
self.tickViewSize = tickViewSize | |
super.init(frame: CGRect(x: 0, y: 0, width: tickViewSize, height: tickViewSize)) | |
self.shapeLayer.lineWidth = 0.10*tickViewSize | |
self.shapeLayer.lineCap = .round | |
self.shapeLayer.lineJoin = .round | |
self.shapeLayer.frame = self.layer.bounds | |
self.shapeLayer.strokeColor = self.lineColor.cgColor | |
self.shapeLayer.backgroundColor = UIColor.clear.cgColor | |
self.shapeLayer.fillColor = nil | |
self.shapeLayer.strokeEnd = 0.0 | |
self.layer.addSublayer(self.shapeLayer) | |
self.layer.cornerRadius = tickViewSize / 2.0 | |
self.translatesAutoresizingMaskIntoConstraints = false | |
} | |
required init?(coder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
override func layoutSubviews() { | |
super.layoutSubviews() | |
self.shapeLayer.frame = self.layer.frame | |
} | |
override var intrinsicContentSize: CGSize { | |
return CGSize(width: self.tickViewSize, height: self.tickViewSize) | |
} | |
override func sizeThatFits(_ size: CGSize) -> CGSize { | |
self.intrinsicContentSize | |
} | |
//MARK: Public methods | |
func animate(to result: AnimatedResultView.Result, completion: (() -> Void)? = nil) { | |
let path = UIBezierPath() | |
path.lineCapStyle = .round | |
switch result { | |
case .success: | |
path.move(to: CGPoint(x: 0.27*tickViewSize, y: 0.52*tickViewSize)) | |
path.addLine(to: CGPoint(x: 0.42*tickViewSize, y: 0.68*tickViewSize)) | |
path.addLine(to: CGPoint(x: 0.68*tickViewSize, y: 0.32*tickViewSize)) | |
case .failure: | |
path.move(to: CGPoint(x: 0.25*tickViewSize, y: 0.25*tickViewSize)) | |
path.addLine(to: CGPoint(x: 0.75*tickViewSize, y: 0.75*tickViewSize)) | |
path.move(to: CGPoint(x: 0.75*tickViewSize, y: 0.25*tickViewSize)) | |
path.addLine(to: CGPoint(x: 0.25*tickViewSize, y: 0.75*tickViewSize)) | |
} | |
self.shapeLayer.path = path.cgPath | |
let animation = CABasicAnimation(keyPath: "strokeEnd") | |
animation.timingFunction = CAMediaTimingFunction(name: .default) | |
animation.fillMode = .both | |
animation.fromValue = 0.0 | |
animation.toValue = 1.0 | |
animation.duration = 0.3 | |
self.shapeLayer.strokeEnd = 1.0 | |
self.shapeLayer.add(animation, forKey: "strokeEnd") | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { | |
completion?() | |
} | |
} | |
} | |
class MyViewController : UIViewController { | |
override func loadView() { | |
let view = UIView() | |
view.backgroundColor = .white | |
let checkMark = AnimatedResultView() | |
checkMark.lineColor = .black | |
checkMark.backgroundColor = .secondarySystemBackground | |
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { | |
checkMark.animate(to: .success) { | |
checkMark.animate(to: .failure) | |
} | |
} | |
view.addSubview(checkMark) | |
self.view = view | |
} | |
} | |
PlaygroundPage.current.liveView = MyViewController() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment