ShutterButton.swift 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. //
  2. // ShutterButton.swift
  3. // WeScan
  4. //
  5. // Created by Boris Emorine on 2/26/18.
  6. // Copyright © 2018 WeTransfer. All rights reserved.
  7. //
  8. import UIKit
  9. @available(iOS 10, *)
  10. /// A simple button used for the shutter.
  11. final class ShutterButton: UIControl {
  12. private let outterRingLayer = CAShapeLayer()
  13. private let innerCircleLayer = CAShapeLayer()
  14. private let outterRingRatio: CGFloat = 0.80
  15. private let innerRingRatio: CGFloat = 0.75
  16. private let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
  17. override var isHighlighted: Bool {
  18. didSet {
  19. if oldValue != isHighlighted {
  20. animateInnerCircleLayer(forHighlightedState: isHighlighted)
  21. }
  22. }
  23. }
  24. // MARL: Life Cycle
  25. override init(frame: CGRect) {
  26. super.init(frame: frame)
  27. layer.addSublayer(outterRingLayer)
  28. layer.addSublayer(innerCircleLayer)
  29. backgroundColor = .clear
  30. isAccessibilityElement = true
  31. accessibilityTraits = UIAccessibilityTraitButton
  32. impactFeedbackGenerator.prepare()
  33. }
  34. required init?(coder aDecoder: NSCoder) {
  35. fatalError("init(coder:) has not been implemented")
  36. }
  37. // MARK: - Drawing
  38. override func draw(_ rect: CGRect) {
  39. outterRingLayer.frame = rect
  40. outterRingLayer.path = pathForOutterRing(inRect: rect).cgPath
  41. outterRingLayer.fillColor = UIColor.white.cgColor
  42. outterRingLayer.rasterizationScale = UIScreen.main.scale
  43. outterRingLayer.shouldRasterize = true
  44. innerCircleLayer.frame = rect
  45. innerCircleLayer.path = pathForInnerCircle(inRect: rect).cgPath
  46. innerCircleLayer.fillColor = UIColor.white.cgColor
  47. innerCircleLayer.rasterizationScale = UIScreen.main.scale
  48. innerCircleLayer.shouldRasterize = true
  49. }
  50. // MARK: - Animation
  51. private func animateInnerCircleLayer(forHighlightedState isHighlighted: Bool) {
  52. let animation = CAKeyframeAnimation(keyPath: "transform")
  53. var values = [CATransform3DMakeScale(1.0, 1.0, 1.0), CATransform3DMakeScale(0.9, 0.9, 0.9), CATransform3DMakeScale(0.93, 0.93, 0.93), CATransform3DMakeScale(0.9, 0.9, 0.9)]
  54. if isHighlighted == false {
  55. values = [CATransform3DMakeScale(0.9, 0.9, 0.9), CATransform3DMakeScale(1.0, 1.0, 1.0)]
  56. }
  57. animation.values = values
  58. animation.isRemovedOnCompletion = false
  59. animation.fillMode = kCAFillModeForwards
  60. animation.duration = isHighlighted ? 0.35 : 0.10
  61. innerCircleLayer.add(animation, forKey: "transform")
  62. impactFeedbackGenerator.impactOccurred()
  63. }
  64. // MARK: - Paths
  65. private func pathForOutterRing(inRect rect: CGRect) -> UIBezierPath {
  66. let path = UIBezierPath(ovalIn: rect)
  67. let innerRect = rect.scaleAndCenter(withRatio: outterRingRatio)
  68. let innerPath = UIBezierPath(ovalIn: innerRect).reversing()
  69. path.append(innerPath)
  70. return path
  71. }
  72. private func pathForInnerCircle(inRect rect: CGRect) -> UIBezierPath {
  73. let rect = rect.scaleAndCenter(withRatio: innerRingRatio)
  74. let path = UIBezierPath(ovalIn: rect)
  75. return path
  76. }
  77. }