// // LoaderButton.swift // LoaderButton // // Created by 黄进文 on 2018/6/11. // Copyright © 2018年 Jovins. All rights reserved. // import UIKit // MARK: - CAGradientLayer extension CAGradientLayer { convenience init(frame: CGRect) { self.init() self.frame = frame } } // MARK: - LoaderButton open class LoaderButton: UIButton { // MARK: - Properties internal var loaderTitle: String? internal var loaderBackgroundNormalImage: UIImage? internal var loaderBackgroundSelectedImage: UIImage? internal var loaderBackgroundDisabledImage: UIImage? internal var loaderBackgroundHighlightedImage: UIImage? internal var loaderNormalImage: UIImage? internal var loaderSelectedImage: UIImage? internal var loaderDisabledImage: UIImage? internal var loaderHighlightedImage: UIImage? internal var loaderBackgroundColor: UIColor? fileprivate var animationDuration: CFTimeInterval = 0 fileprivate var isAnimating: Bool = false fileprivate var loaderType: LoaderType = .rotateChase @IBInspectable var cornerRadius: CGFloat = 0 { willSet { layer.cornerRadius = newValue } } public var loaderColor: UIColor = UIColor.gray public var title: String? { get { return self.title(for: .normal) } set { self.setTitle(newValue, for: .normal) } } // MARK: - Initializers public init() { super.init(frame: .zero) setup() } public override init(frame: CGRect) { super.init(frame: frame) setup() } public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setup() } open override func layoutSubviews() { super.layoutSubviews() clipsToBounds = true gradientLayer.frame = self.bounds } // MARK: - Method func setup() { if self.cornerRadius == 0 { self.cornerRadius = self.layer.cornerRadius } self.layer.cornerRadius = self.cornerRadius self.layer.masksToBounds = true if self.image(for: .normal) != nil && self.title != nil { let space: CGFloat = 10 self.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: space) self.titleEdgeInsets = UIEdgeInsets(top: 0, left: space, bottom: 0, right: 0) } } // MARK: lazy internal lazy var gradientLayer: CAGradientLayer = { let gradient = CAGradientLayer(frame: self.frame) gradient.startPoint = CGPoint(x: 0.0, y: 0.0) gradient.endPoint = CGPoint(x: 1.0, y: 1.0) self.layer.insertSublayer(gradient, at: 0) return gradient }() public var gradientColors: [UIColor]? { willSet { gradientLayer.colors = newValue?.map({$0.cgColor}) } } } // MARK: - Animation public extension LoaderButton { // MARK: - Public Method /// Start Animation /// /// - Parameters: /// - LoaderType: Loader Type ( rotateChase(default), lineFade, circleStroke) /// - loader color: color of loader (default = gray) /// - complete: complation block (call after animation start) func startAnimate(loaderType: LoaderType = .rotateChase, loaderColor: UIColor = .gray, complete: (() -> Void)?) { if self.cornerRadius == 0 { self.cornerRadius = self.layer.cornerRadius } self.stopAnimation() isAnimating = true self.loaderColor = loaderColor self.loaderType = loaderType self.layer.cornerRadius = self.frame.height * 0.5 self.collapseAnimation(complete: complete) } /// stop Animation and set button in actual state /// /// - Parameter complete: complation block (call after animation Stop) func stopAnimate(complete: (() -> Void)?) { DispatchQueue.main.asyncAfter(deadline: .now() + animationDuration) { self.backToDefault(complete: complete) } } // MARK: - Private Method /// stop animation of specific loader private func stopAnimation() { if layer.sublayers != nil { for item in layer.sublayers! where item is LoaderLayer { item.removeAllAnimations() item.removeFromSuperlayer() } } } /// collapse animation private func collapseAnimation(complete: (() -> Void)?) { loaderTitle = title title = "" loaderBackgroundNormalImage = self.backgroundImage(for: .normal) loaderBackgroundSelectedImage = self.backgroundImage(for: .selected) loaderBackgroundDisabledImage = self.backgroundImage(for: .disabled) loaderBackgroundHighlightedImage = self.backgroundImage(for: .highlighted) loaderNormalImage = self.image(for: .normal) loaderDisabledImage = self.image(for: .disabled) loaderSelectedImage = self.image(for: .selected) loaderHighlightedImage = self.image(for: .highlighted) loaderBackgroundColor = self.backgroundColor self.setImage(nil, for: .normal) self.setImage(nil, for: .disabled) self.setImage(nil, for: .selected) self.setImage(nil, for: .highlighted) isUserInteractionEnabled = false let animation = CABasicAnimation(keyPath: "bounds.size.width") animation.fromValue = bounds.width animation.toValue = bounds.height animation.duration = animationDuration animation.fillMode = .both animation.isRemovedOnCompletion = false layer.add(animation, forKey: animation.keyPath) self.perform(#selector(startLoader), with: nil, afterDelay: animationDuration) DispatchQueue.main.asyncAfter(deadline: .now() + animationDuration) { if complete != nil { complete!() } } } /// back to default private func backToDefault(complete: (() -> Void)?) { if !isAnimating { return } self.stopAnimation() setTitle(loaderTitle, for: .normal) self.setBackgroundImage(self.loaderNormalImage, for: .normal) self.setBackgroundImage(self.loaderDisabledImage, for: .disabled) self.setBackgroundImage(self.loaderSelectedImage, for: .selected) self.setBackgroundImage(self.loaderHighlightedImage, for: .highlighted) self.setImage(self.loaderNormalImage, for: .normal) self.setImage(self.loaderDisabledImage, for: .disabled) self.setImage(self.loaderSelectedImage, for: .selected) self.setImage(self.loaderHighlightedImage, for: .highlighted) isUserInteractionEnabled = true let animation = CABasicAnimation(keyPath: "bounds.size.width") animation.fromValue = frame.height animation.toValue = frame.width animation.duration = animationDuration animation.fillMode = .forwards animation.isRemovedOnCompletion = false layer.add(animation, forKey: animation.keyPath) isAnimating = false self.layer.cornerRadius = self.cornerRadius if complete != nil { complete!() } } /// start laoding @objc private func startLoader() { let animation: LoaderButtonAnimationDelegate = self.loaderType.animation() animation.setupLoaderButtonAnimation(layer: self.layer, frame: self.bounds, color: self.loaderColor) } }