ShadowDesignable.swift 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. //
  2. // Created by Jake Lin on 11/18/15.
  3. // Copyright © 2015 IBAnimatable. All rights reserved.
  4. //
  5. import UIKit
  6. /**
  7. These properties are not able to render in IB correctly, it maybe a bug of IB.
  8. To use them, `UIView`'s `clipsToBounds` and `CALayer`'s `masksToBounds` (`Clip Subviews` in IB) must be `false`,
  9. */
  10. public protocol ShadowDesignable: class {
  11. /**
  12. `color` when using with `box-shadow`
  13. */
  14. var shadowColor: UIColor? { get set }
  15. /**
  16. Radius in `box-shadow`
  17. */
  18. var shadowRadius: CGFloat { get set }
  19. /**
  20. Opacity in `box-shadow`: from 0 to 1
  21. */
  22. var shadowOpacity: CGFloat { get set }
  23. /**
  24. Offset in `box-shadow`. `x` is horizontal offset and `y` is vertical offset
  25. */
  26. var shadowOffset: CGPoint { get set }
  27. }
  28. // MARK: - UIView
  29. extension ShadowDesignable where Self: UIView {
  30. public func configureShadowColor() {
  31. configureShadowColor(in: self)
  32. }
  33. public func configureShadowRadius() {
  34. configureShadowRadius(in: self)
  35. }
  36. public func configureShadowOpacity() {
  37. configureShadowOpacity(in: self)
  38. }
  39. public func configureShadowOffset() {
  40. configureShadowOffset(in: self)
  41. }
  42. public func configureMaskShadow() {
  43. configureMaskShadow(in: self)
  44. }
  45. }
  46. // MARK: - Common
  47. extension ShadowDesignable {
  48. func configureShadowColor(in view: UIView) {
  49. if let shadowColor = shadowColor {
  50. commonSetup(in: view)
  51. view.layer.shadowColor = shadowColor.cgColor
  52. }
  53. }
  54. func configureShadowRadius(in view: UIView) {
  55. if !shadowRadius.isNaN && shadowRadius > 0 {
  56. commonSetup(in: view)
  57. view.layer.shadowRadius = shadowRadius
  58. }
  59. }
  60. func configureShadowOpacity(in view: UIView) {
  61. if !shadowOpacity.isNaN && shadowOpacity >= 0 && shadowOpacity <= 1 {
  62. commonSetup(in: view)
  63. view.layer.shadowOpacity = Float(shadowOpacity)
  64. }
  65. }
  66. func configureShadowOffset(in view: UIView) {
  67. if !shadowOffset.x.isNaN {
  68. commonSetup(in: view)
  69. view.layer.shadowOffset.width = shadowOffset.x
  70. }
  71. if !shadowOffset.y.isNaN {
  72. commonSetup(in: view)
  73. view.layer.shadowOffset.height = shadowOffset.y
  74. }
  75. }
  76. func configureMaskShadow(in view: UIView) {
  77. // if a `layer.mask` is specified, add a new shadow layer to display the shadow to match the mask shape.
  78. guard let mask = view.layer.mask as? CAShapeLayer else {
  79. return
  80. }
  81. commonSetup(in: view)
  82. // Clear default layer borders
  83. view.layer.shadowColor = nil
  84. view.layer.shadowRadius = 0
  85. view.layer.shadowOpacity = 0
  86. // Remove any previous shadow layer
  87. view.layer.superlayer?.sublayers?.filter { $0.name == "shadowLayer-\(Unmanaged.passUnretained(self))" }
  88. .forEach { $0.removeFromSuperlayer() }
  89. // Create new layer with object's memory reference to make this string unique. Otherwise common name will remove all the shadow layers as it's adding in layer's superview.
  90. let shadowLayer = CAShapeLayer()
  91. shadowLayer.name = "shadowLayer-\(Unmanaged.passUnretained(self))"
  92. shadowLayer.frame = view.frame
  93. // Configure shadow properties
  94. if let shadowColor = shadowColor {
  95. shadowLayer.shadowColor = shadowColor.cgColor
  96. }
  97. if !shadowRadius.isNaN && shadowRadius > 0 {
  98. shadowLayer.shadowRadius = shadowRadius
  99. }
  100. if !shadowOpacity.isNaN && shadowOpacity >= 0 && shadowOpacity <= 1 {
  101. shadowLayer.shadowOpacity = Float(shadowOpacity)
  102. }
  103. if !shadowOffset.x.isNaN {
  104. shadowLayer.shadowOffset.width = shadowOffset.x
  105. }
  106. if !shadowOffset.y.isNaN {
  107. shadowLayer.shadowOffset.height = shadowOffset.y
  108. }
  109. shadowLayer.shadowPath = mask.path
  110. // Add to layer's superview in order to render shadow otherwise it will clip out due to mask layer.
  111. view.layer.superlayer?.insertSublayer(shadowLayer, below: view.layer)
  112. }
  113. private func commonSetup(in view: UIView) {
  114. // Need to set `layer.masksToBounds` to `false`.
  115. // If `layer.masksToBounds == true` then shadow doesn't work any more.
  116. if view.layer.masksToBounds {
  117. view.layer.masksToBounds = false
  118. }
  119. }
  120. }