How to add Gecture for UIView Extension method swift - swift

Hi I want to give the PanGecture Swipe action for UIView on the top of UIViewControllers.for that one I write one common method in view controller extension but its make the entire viewcontroller is swipe.
I want to give the swipe action only for UIView please help to me any idea to change the following code to UIView Extension.
extension UIViewController{
#objc func pangectureRecognizerDismissoneScreen(_ sender: UIPanGestureRecognizer){
var initialTouchPoint: CGPoint = CGPoint(x: 0,y: 0)
let touchPoint = sender.location(in: self.view?.window)
if sender.state == UIGestureRecognizerState.began {
initialTouchPoint = touchPoint
} else if sender.state == UIGestureRecognizerState.changed {
if touchPoint.y - initialTouchPoint.y > 0 {
self.view.frame = CGRect(x: 0, y: touchPoint.y - initialTouchPoint.y, width: self.view.frame.size.width, height: self.view.frame.size.height)
}
} else if sender.state == UIGestureRecognizerState.ended || sender.state == UIGestureRecognizerState.cancelled {
if touchPoint.y - initialTouchPoint.y > 100 {
self.view.backgroundColor = UIColor.clear
self.dismiss(animated: true, completion: nil)
} else {
UIView.animate(withDuration: 0.3, animations: {
self.view.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
})
}
}
}
}

You can add this UIView extension
import UIKit
import RxSwift
struct AssociatedKeys {
static var actionState: UInt8 = 0
}
typealias ActionTap = () -> Void
extension UIView {
var addAction: ActionTap? {
get {
guard let value = objc_getAssociatedObject(self, &AssociatedKeys.actionState) as? ActionTap else {
return nil
}
return value
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.actionState, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
self.tapWithAnimation()
}
}
func tapWithAnimation() {
self.gestureRecognizers?.removeAll()
let longTap = UILongPressGestureRecognizer(target: self, action: #selector(viewLongTap(_:)))
longTap.minimumPressDuration = 0.035
longTap.delaysTouchesBegan = true
self.addGestureRecognizer(longTap)
}
#objc
func viewLongTap(_ gesture: UILongPressGestureRecognizer) {
if gesture.state != .ended {
animateView(alpha: 0.3)
return
} else if gesture.state == .ended {
let touchLocation = gesture.location(in: self)
if self.bounds.contains(touchLocation) {
animateView(alpha: 1)
addAction?()
return
}
}
animateView(alpha: 1)
}
fileprivate func animateView(alpha: CGFloat) {
UIView.transition(with: self, duration: 0.3,
options: .transitionCrossDissolve, animations: {
self.subviews.forEach { subView in
subView.alpha = alpha
}
})
}
}
Example:
myView.addAction = {
print("click")
}

Use this class extension.
//Gesture extention
extension UIGestureRecognizer {
#discardableResult convenience init(addToView targetView: UIView,
closure: #escaping () -> Void) {
self.init()
GestureTarget.add(gesture: self,
closure: closure,
toView: targetView)
}
}
fileprivate class GestureTarget: UIView {
class ClosureContainer {
weak var gesture: UIGestureRecognizer?
let closure: (() -> Void)
init(closure: #escaping () -> Void) {
self.closure = closure
}
}
var containers = [ClosureContainer]()
convenience init() {
self.init(frame: .zero)
isHidden = true
}
class func add(gesture: UIGestureRecognizer, closure: #escaping () -> Void,
toView targetView: UIView) {
let target: GestureTarget
if let existingTarget = existingTarget(inTargetView: targetView) {
target = existingTarget
} else {
target = GestureTarget()
targetView.addSubview(target)
}
let container = ClosureContainer(closure: closure)
container.gesture = gesture
target.containers.append(container)
gesture.addTarget(target, action: #selector(GestureTarget.target(gesture:)))
targetView.addGestureRecognizer(gesture)
}
class func existingTarget(inTargetView targetView: UIView) -> GestureTarget? {
for subview in targetView.subviews {
if let target = subview as? GestureTarget {
return target
}
}
return nil
}
func cleanUpContainers() {
containers = containers.filter({ $0.gesture != nil })
}
#objc func target(gesture: UIGestureRecognizer) {
cleanUpContainers()
for container in containers {
guard let containerGesture = container.gesture else {
continue
}
if gesture === containerGesture {
container.closure()
}
}
}
}
EDIT 1 :-
Use like this
UIGestureRecognizer.init(addToView: yourView, closure: {
// your code
})

Related

Protocol reusable Spinner one liner progressview

This is my code at the moment I instead of using progressView as
self.progressView = showProgressView()
I want to use it as self.showProgress() as a one liner
this is my current protocol
protocol Loadable {
var progressView: ProgressView? { get }
func showProgressView() -> ProgressView
func hideProgressView()
}
extension Loadable where Self: UIViewController {
func showProgressView() -> ProgressView {
let pv = ProgressView(frame: CGRect.zero)
view.addSubview(pv)
pv.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(pv.setConstraints(boundsOf: view))
UIView.animate(withDuration: 0.3) {
pv.alpha = 1.0
}
return pv
}
func hideProgressView() {
guard let progressView = progressView else { return }
UIView.animate(withDuration: 0.6) {
progressView.alpha = 0.0
} completion: { finished in
progressView.removeFromSuperview()
}
}
}```
//this is my current usage on the code
#objc private func loadData() {
print("Calling API")
self.progressView = showProgressView()
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0){
self.civilizationData()
self.hideProgressView()
}
}
//tried using this method
protocol Loadable {
var progressView: ProgressView? { get }
func showProgressView()
func hideProgressView()
}
extension Loadable where Self: UIViewController {
func showProgressView() {
let progressView = ProgressView(frame: CGRect.zero)
view.addSubview(progressView)
progressView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(progressView.setConstraints(boundsOf: view))
UIView.animate(withDuration: 0.3) {
progressView.alpha = 1.0
}
}
//but the added subview of my progressview doesn't disappear
protocol Loadable {
var progressView: ProgressView? { get }
func showProgressView()
func hideProgressView()
}
extension Loadable where Self: UIViewController {
func showProgressView() {
let progressView = ProgressView(frame: CGRect.zero)
view.addSubview(progressView)
progressView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(progressView.setConstraints(boundsOf: view))
UIView.animate(withDuration: 0.3) {
progressView.alpha = 1.0
}
}

How to change value of label from another UICollectionViewCell

What if the UILabel is in class A and the didTapRightutton that will animate it is in class B?
the percentDiscountLabel is in RandomizeDealsCollectionViewCell. This should animate into fade appear if I tap didTapRightutton which is in a different VC called RandomizeDealsViewController
How do I call the function that is inside RandomizeDealsCollectionViewCell to animate the percentDiscountLabel? Is there other way to do this?
class RandomizeDealsCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var percentDiscountLabel: UILabel!
func animatePercentDiscountLabel(deals: String) {
self.percentDiscountLabel.alpha = 0.6
self.percentDiscountLabel.isHidden = false
UIView.animate(withDuration: 0.6, delay: 0, options: .curveEaseInOut, animations: {
self.percentDiscountLabel.alpha = 1.0
}) { (isCompleted) in
}
percentDiscountLabel.text = deals
}
}
class RandomizeDealsViewController: UIViewController {
private var centeredCollectionViewFlowLayout: CenteredCollectionViewFlowLayout!
#IBAction func didTapRightButton(_ sender: Any) {
guard let indexCard = centeredCollectionViewFlowLayout.currentCenteredPage else { return }
if (indexCard > 0) {
centeredCollectionViewFlowLayout.scrollToPage(index: indexCard + 1, animated: true)
// should call the animation function here
}
}
}
If indexCard is indexPath of collectionViewCell which you want to animate,
You can call your cell like ->
in RandomizeDealsViewController
#IBAction func didTapRightButton(_ sender: Any) {
guard let indexCard = centeredCollectionViewFlowLayout.currentCenteredPage else { return }
if (indexCard > 0) {
centeredCollectionViewFlowLayout.scrollToPage(index: indexCard + 1, animated: true)
// should call the animation function here
let cell = collectionView!.cellForItemAtIndexPath(indexCard) as? RandomizeDealsCollectionViewCell
cell?.animatePercentDiscountLabel(deals: "deals")
}
}

About a clean common method

It is a process to slide the screen when the keyboard appears, but this process is described by many controllers.
The processing in if and else changes according to each controller.
Is there a way to cleanly share it?
Also, where and how should we commonize?
func keyboardWillChangeFrame(_ notification: Notification) {
if let endFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
var keyboardHeight = UIScreen.main.bounds.height - endFrame.origin.y
if #available(iOS 11, *) {
if keyboardHeight > 0 {
view.addGestureRecognizer(ui.viewTapGesture)
ui.isHiddenSubmitBtn(false)
ui.isHiddenTextCount(false)
keyboardHeight = keyboardHeight - view.safeAreaInsets.bottom + ui.submitBtn.frame.height + 8
} else {
view.removeGestureRecognizer(ui.viewTapGesture)
ui.isHiddenSubmitBtn(true)
ui.isHiddenTextCount(true)
}
}
ui.textViewBottomConstraint.constant = -keyboardHeight - 8
view.layoutIfNeeded()
}
}
You can use class and protocols, example code below
// Firstly Create Notifier Keyboard
class KeyboardNotifier: NSObject {
// We notify this closures
var keyboardPresent: ((_ height: CGFloat) -> Void)?
var keyboardDismiss: ((_ height: CGFloat) -> Void)?
// Add Notification
func listenKeyboard() {
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillChangeFramex(_:)),
name: UIApplication.keyboardWillChangeFrameNotification,
object: nil)
}
// Handle Notification and call closures
#objc func keyboardWillChangeFramex(_ notification: Notification) {
if let endFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight = UIScreen.main.bounds.height - endFrame.origin.y
if #available(iOS 11, *) {
DispatchQueue.main.async { [weak self] in
if keyboardHeight > 0 {
self?.keyboardPresent?(keyboardHeight)
} else {
self?.keyboardDismiss?(keyboardHeight)
}
}
}
}
}
}
// Create a listener protocol
protocol KeyboardListener {
func keyboardPresent(_ height: CGFloat)
func keyboardDismiss(_ height: CGFloat)
var keyboardNotifier: KeyboardNotifier! { get set }
}
// We need a extension for we don't want all viewController
extension KeyboardListener where Self: UIViewController {
// We need call this function viewDidLoad later
func listenKeyboard(keyboardNotifier: KeyboardNotifier) {
keyboardNotifier.keyboardDismiss = { [weak self] height in
self?.keyboardDismiss(height)
}
keyboardNotifier.keyboardPresent = { [weak self] height in
self?.keyboardPresent(height)
}
}
}
// A XViewController want's to listen keyboard
class XViewController: UIViewController, KeyboardListener {
var keyboardNotifier: KeyboardNotifier!
override func viewDidLoad() {
super.viewDidLoad()
// We need instance for life cycle
keyboardNotifier = KeyboardNotifier()
listenKeyboard(keyboardNotifier: keyboardNotifier)
}
func keyboardPresent(_ height: CGFloat) {
// TODO UI
}
func keyboardDismiss(_ height: CGFloat) {
// TODO UI
}
}
// If you want all viewController listen keyboard
class BaseViewController: UIViewController, KeyboardListener {
var keyboardNotifier: KeyboardNotifier!
override func viewDidLoad() {
super.viewDidLoad()
// We need instance for life cycle
keyboardNotifier = KeyboardNotifier()
listenKeyboard(keyboardNotifier: keyboardNotifier)
}
func keyboardPresent(_ height: CGFloat) {
}
func keyboardDismiss(_ height: CGFloat) {
}
}
// This Y ViewController from BaseViewController
class YViewController: BaseViewController {
override func keyboardPresent(_ height: CGFloat) {
}
override func keyboardDismiss(_ height: CGFloat) {
}
}

Xcode / Swift | Use UIViewController transition for UINavigationController

Storyboard cutting
The steps that I did were:
Create the + and x buttons in the Controllers
Create simple outlets in the Controllers
Create an action in the Controller for the x (dismiss)-button
#IBAction func button_close_pressed(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
Create a segue on the Storyboard from the + button to the targetViewController.
Create the transition class.
import UIKit
class CircularTransition: NSObject {
var circle = UIView()
var startingPoint = CGPoint.zero {
didSet {
circle.center = startingPoint
}
}
var circleColor = UIColor.red
var duration = 3.0
enum CircularTransitionMode: Int {
case present, dismiss, pop
}
var transitionMode: CircularTransitionMode = .present
}
extension CircularTransition: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
if transitionMode == .present {
if let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to) {
let viewCenter = presentedView.center
let viewSize = presentedView.frame.size
circle = UIView()
circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
circle.layer.cornerRadius = circle.frame.size.height / 2
circle.center = startingPoint
circle.backgroundColor = circleColor
circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
containerView.addSubview(circle)
presentedView.center = startingPoint
presentedView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
presentedView.alpha = 0
containerView.addSubview(presentedView)
UIView.animate(withDuration: duration, animations: {
self.circle.transform = CGAffineTransform.identity
presentedView.transform = CGAffineTransform.identity
presentedView.alpha = 1
presentedView.center = viewCenter
}, completion: { (success: Bool) in
transitionContext.completeTransition(success)
})
}
} else {
let transitionModeKey = (transitionMode == .pop) ? UITransitionContextViewKey.to : UITransitionContextViewKey.from
if let returningView = transitionContext.view(forKey: transitionModeKey) {
let viewCenter = returningView.center
let viewSize = returningView.frame.size
circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
circle.layer.cornerRadius = circle.frame.size.height / 2
circle.center = startingPoint
UIView.animate(withDuration: duration, animations: {
self.circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
returningView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
returningView.center = self.startingPoint
returningView.alpha = 0
if self.transitionMode == .pop {
containerView.insertSubview(returningView, belowSubview: returningView)
containerView.insertSubview(self.circle, belowSubview: returningView)
}
}, completion: { (success: Bool) in
returningView.center = viewCenter
returningView.removeFromSuperview()
self.circle.removeFromSuperview()
transitionContext.completeTransition(success)
})
}
}
}
func frameForCircle(withViewCenter viewCenter: CGPoint, size viewSize: CGSize, startPoint: CGPoint) -> CGRect {
let xLength = fmax(startPoint.x, viewSize.width - startPoint.x)
let yLength = fmax(startPoint.y, viewSize.height - startPoint.y)
let offsetVector = sqrt(xLength * xLength + yLength * yLength) * 2
let size = CGSize(width: offsetVector, height: offsetVector)
return CGRect(origin: CGPoint.zero, size: size)
}
}
In the source Controller I added ... to the class
UIViewControllerTransitioningDelegate
Then
let transition = CircularTransition()
And last but not least
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let eventVC = segue.destination as! EventViewController
eventVC.transitioningDelegate = self
eventVC.modalPresentationStyle = .custom
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.transitionMode = .present
transition.startingPoint = self.button_addEvent.center
transition.circleColor = self.button_addEvent.backgroundColor!
return transition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.transitionMode = .dismiss
transition.startingPoint = self.button_addEvent.center
transition.circleColor = self.button_addEvent.backgroundColor!
return transition
}
Now, why doesn't that work? I only can think of that the problem comes with the Navigation Controller instead of a normal ViewController, because with a normal one it works fine. But I really have no idea hwo to change the code to match with the Navigation Controller (source Controller).
Kind regards and thank you!
You need to implement UINavigationControllerDelegate in your viewController
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
switch operation {
case .push:
transition.transitionMode = .present
transition.startingPoint = self.button_addEvent.center
transition.circleColor = self.button_addEvent.backgroundColor!
return transition
default:
transition.transitionMode = .dismiss
transition.startingPoint = self.button_addEvent.center
transition.circleColor = self.button_addEvent.backgroundColor!
return transition
}
}

Swift2: TKSubmitTransitionButton. How do i stop a button from transition/ animating when user login/ signup are incorrect

I have tried a number of times and the best i get is there would be an animation which never cancels or stop regardless of the command i use.
After following #Mattias example, i updated my code and looks something like this:
// DESIGN ANIMATION... TKTRANSITIONSUBMITBUTTON
#IBOutlet weak var btnFromNib: TKTransitionSubmitButton!
#IBAction func onTapButton(sender: AnyObject) {
btnFromNib.startLoadingAnimation()
if let email = self.emailField.text where email != "", let password = self.passwordField.text where password != "" {
DataService.ds.REF_BASE.authUser(email, password: password, withCompletionBlock: { error, authData in
if error != nil {
self.btnFromNib.returnToOriginalState()
if error.code == STATUS_ACCOUNT_NONEXIST {
self.showErrorAlert("This User does not exist", msg: "Please Sign Up")
} else {
}
} else {
self.btnFromNib.startFinishAnimation(1, completion: {
let myTabbarController = self.storyboard?.instantiateViewControllerWithIdentifier("myTabbarController") as! UITabBarController
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = myTabbarController
myTabbarController.transitioningDelegate = self
})
}
})
}
}
The button yet keeps spinning / animating without stopping. After checking the custom animation class the function inherits from :
public func startLoadingAnimation() {
self.cachedTitle = titleForState(.Normal)
self.setTitle("", forState: .Normal)
self.shrink()
NSTimer.schedule(delay: shrinkDuration - 0.25) { timer in
self.spiner.animation()
}
}
public func startFinishAnimation(delay: NSTimeInterval, completion:(()->())?) {
NSTimer.schedule(delay: delay) { timer in
self.didEndFinishAnimation = completion
self.expand()
self.spiner.stopAnimation()
}
}
public func animate(duration: NSTimeInterval, completion:(()->())?) {
startLoadingAnimation()
startFinishAnimation(duration, completion: completion)
}
public override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
let a = anim as! CABasicAnimation
if a.keyPath == "transform.scale" {
didEndFinishAnimation?()
NSTimer.schedule(delay: 1) { timer in
self.returnToOriginalState()
}
}
}
func returnToOriginalState() {
self.layer.removeAllAnimations()
self.setTitle(self.cachedTitle, forState: .Normal)
}
I noticed it had a public overide func animationDidStop(anim: CAAnimation, finished: Bool) to be the function to stop the animation. But when i use it, i get this error!
How do i rightfully get this to work? ...
Thanks in Advance
** UPDATED QUESTION **
I checked the code of TKTransitionSubmitButton and there are public methods called startLoadingAnimation(), returnToOriginalState() and startFinishAnimation().
I suggest:
Button tapped
startLoadingAnimation()
Check credentials
If wrong/error: returnToOriginalState()
If correct: startFinishAnimation()
Transition, from TKTransitionSubmitButton documentation:
btn.startFinishAnimation {
//Your Transition
let secondVC = SecondViewController()
secondVC.transitioningDelegate = self
self.presentViewController(secondVC, animated: true, completion: nil)
}
Edit: As far as I can see .animate() of the class calls both the start and finish animation, and that's why you can't cancel it.
EDIT2 This one works as intended for me (however I'm not sure about the static cornerRadius)
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var submitButton: TKTransitionSubmitButton!
override func viewDidLoad() {
super.viewDidLoad()
submitButton.layer.cornerRadius = 15
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func buttonPressed(sender: AnyObject) {
submitButton.startLoadingAnimation()
delay(2, closure: {
self.checkCredentials()
})
}
func checkCredentials()
{
//FAKING WRONG CREDENTIALS
let userAndPasswordCorrect = false
if !userAndPasswordCorrect
{
submitButton.returnToOriginalState()
//Alert or whatever
}
}
//Faking network delay
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
}
I checked the code of TKTransitionSubmitButton.
I resolve this issue. please try to stop animation under DispatchQueue.main.async and working well.
'func apicallforLogin() {
let urlString = "http://"
guard let requestUrl = URL(string:urlString) else { return }
let request = URLRequest(url:requestUrl)
let task = URLSession.shared.dataTask(with: request) {
(data, response, error) in
if error == nil,let usableData = data {
DispatchQueue.main.async {
self.btnLogin.startFinishAnimation(0.1, completion: {
let HomeVC = self.storyboard?.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
HomeVC.transitioningDelegate = self
self.navigationController?.pushViewController(HomeVC, animated: false)
})
}
print(usableData)
}else{
DispatchQueue.main.async {
self.btnLogin.returnToOriginalState()
}
}
}
task.resume()'
Well you might have got the answer by now but just I would like to give my answer. You can just copy below code and everything will work fine. I have made the change and had created the pull request for this issue.
import Foundation
import UIKit
#IBDesignable
open class TKTransitionSubmitButton : UIButton, UIViewControllerTransitioningDelegate, CAAnimationDelegate {
lazy var spiner: SpinerLayer! = {
let s = SpinerLayer(frame: self.frame)
return s
}()
#IBInspectable open var spinnerColor: UIColor = UIColor.white {
didSet {
spiner.spinnerColor = spinnerColor
}
}
open var didEndFinishAnimation : (()->())? = nil
let springGoEase = CAMediaTimingFunction(controlPoints: 0.45, -0.36, 0.44, 0.92)
let shrinkCurve = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
let expandCurve = CAMediaTimingFunction(controlPoints: 0.95, 0.02, 1, 0.05)
let shrinkDuration: CFTimeInterval = 0.1
#IBInspectable open var normalCornerRadius:CGFloat = 0.0 {
didSet {
self.layer.cornerRadius = normalCornerRadius
}
}
var cachedTitle: String?
var isAnimating = false
public override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
public required init!(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.setup()
}
func setup() {
self.clipsToBounds = true
spiner.spinnerColor = spinnerColor
}
open func startLoadingAnimation() {
self.isAnimating = true
self.cachedTitle = title(for: UIControlState())
self.setTitle("", for: UIControlState())
self.layer.addSublayer(spiner)
// Animate
self.cornerRadius()
self.shrink()
_ = Timer.schedule(delay: self.shrinkDuration - 0.25) { timer in
self.spiner.animation()
}
}
open func startFinishAnimation(_ delay: TimeInterval,_ animation: CAMediaTimingFunction, completion:(()->())?) {
self.isAnimating = true
_ = Timer.schedule(delay: delay) { timer in
self.didEndFinishAnimation = completion
self.expand(animation)
self.spiner.stopAnimation()
}
}
open func animate(_ duration: TimeInterval,_ animation: CAMediaTimingFunction, completion:(()->())?) {
startLoadingAnimation()
startFinishAnimation(duration, animation, completion: completion)
}
open func setOriginalState() {
self.returnToOriginalState()
self.spiner.stopAnimation()
}
public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
let a = anim as! CABasicAnimation
if a.keyPath == "transform.scale" {
didEndFinishAnimation?()
_ = Timer.schedule(delay: 1) { timer in
self.returnToOriginalState()
}
}
}
open func returnToOriginalState() {
self.spiner.removeFromSuperlayer()
self.layer.removeAllAnimations()
self.setTitle(self.cachedTitle, for: UIControlState())
self.spiner.stopAnimation()
self.isAnimating = false
}
func cornerRadius() {
let cornerRadiusAnim = CABasicAnimation(keyPath: "cornerRadius")
// cornerRadiusAnim.fromValue = frame.width
cornerRadiusAnim.toValue = frame.height/2
cornerRadiusAnim.duration = shrinkDuration
cornerRadiusAnim.timingFunction = shrinkCurve
cornerRadiusAnim.fillMode = kCAFillModeForwards
cornerRadiusAnim.isRemovedOnCompletion = false
layer.add(cornerRadiusAnim, forKey: cornerRadiusAnim.keyPath)
}
func shrink() {
let shrinkAnim = CABasicAnimation(keyPath: "bounds.size.width")
shrinkAnim.beginTime = CACurrentMediaTime() + 0.1
shrinkAnim.fromValue = frame.width
shrinkAnim.toValue = frame.height
shrinkAnim.duration = shrinkDuration
shrinkAnim.timingFunction = shrinkCurve
shrinkAnim.fillMode = kCAFillModeForwards
shrinkAnim.isRemovedOnCompletion = false
layer.add(shrinkAnim, forKey: shrinkAnim.keyPath)
}
func expand(_ animation: CAMediaTimingFunction) {
let expandAnim = CABasicAnimation(keyPath: "transform.scale")
expandAnim.fromValue = 1.0
expandAnim.toValue = 26.0
expandAnim.timingFunction = animation
expandAnim.duration = 0.3
expandAnim.delegate = self
expandAnim.fillMode = kCAFillModeForwards
expandAnim.isRemovedOnCompletion = false
layer.add(expandAnim, forKey: expandAnim.keyPath)
}
}