BlurDesignable.swift 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. //
  2. // Created by Jake Lin on 11/23/15.
  3. // Copyright © 2015 IBAnimatable. All rights reserved.
  4. //
  5. import UIKit
  6. /// A protocol provides blur designable feature.
  7. public protocol BlurDesignable: class {
  8. /**
  9. Blur effect style: `extraLight`, `light`, `dark`, `regular` (iOS 10+) or `prominent` (iOS 10+).
  10. */
  11. var blurEffectStyle: UIBlurEffect.Style? { get set }
  12. /**
  13. Vibrancy effect style: `extraLight`, `light`, `dark`, `regular` (iOS 10+) or `prominent` (iOS 10+). Once specify the vibrancy effect style, all subviews of blur effect view will apply this vibrancy effect. The property should set once. Because once we use `UIVisualEffectView`, Apple uses a private class like `_UIVisualEffectContentView` to contain subviews which will change the view hierarchy. After that, we are not able to restore the original view hierarchy. Also, the Auto Layout constraints will be broken when the system change the view hierarchy. We need to manually re-set up the constraints or use Autoresizing masks to layout the subviews.
  14. */
  15. var vibrancyEffectStyle: UIBlurEffect.Style? { get set }
  16. /**
  17. Blur opacity: The opacity of UI element using blur effect. The default value is 1.0 if not specified. There is some performance penalty when we use this property.
  18. */
  19. var blurOpacity: CGFloat { get set }
  20. }
  21. // MARK: - UIView
  22. extension BlurDesignable where Self: UIView {
  23. public func configureBlurEffectStyle() {
  24. configureBlurEffectStyle(for: self, in: self)
  25. }
  26. }
  27. // MARK: - UITableView
  28. extension BlurDesignable where Self: UITableView {
  29. public func configureBackgroundBlurEffectStyle() {
  30. guard let blurableView = backgroundView else {
  31. separatorEffect = nil
  32. return
  33. }
  34. configureBlurEffectStyle(for: blurableView, in: self)
  35. guard let blurEffectStyle = blurEffectStyle else {
  36. return
  37. }
  38. if let style = vibrancyEffectStyle {
  39. separatorEffect = UIVibrancyEffect(blurEffect: UIBlurEffect(style: style))
  40. } else {
  41. separatorEffect = UIBlurEffect(style: blurEffectStyle)
  42. }
  43. }
  44. }
  45. // MARK: - Common
  46. extension BlurDesignable {
  47. func configureBlurEffectStyle(in view: UIView) {
  48. configureBlurEffectStyle(for: view, in: view)
  49. }
  50. // configureBlurEffectStyle method, should be called in layoutSubviews() method
  51. func configureBlurEffectStyle(for blurableView: UIView, in view: UIView) {
  52. // Used for caching the previous visual effect view
  53. var privateVisualEffectView: PrivateVisualEffectView?
  54. // Remove the existing visual effect view
  55. blurableView.subviews.compactMap { $0 as? PrivateVisualEffectView }.forEach {
  56. privateVisualEffectView = $0 // Cache it for the subviews
  57. $0.removeFromSuperview()
  58. }
  59. guard let blurEffectStyle = blurEffectStyle else {
  60. return
  61. }
  62. let blurEffectView = makeVisualEffectView(effect: UIBlurEffect(style: blurEffectStyle), in: view)
  63. // If `vibrancyEffectStyle` has been set, add `vibrancyEffectView` into `blurEffectView`.
  64. if let vibrancyEffectStyle = vibrancyEffectStyle {
  65. let blurEffectStyleForVibrancy = UIBlurEffect(style: vibrancyEffectStyle)
  66. let vibrancyEffectView = makeVisualEffectView(effect: UIVibrancyEffect(blurEffect: blurEffectStyleForVibrancy), in: view)
  67. blurableView.subviews.forEach {
  68. vibrancyEffectView.contentView.addSubview($0)
  69. }
  70. // If privateVisualEffectView is not `nil`, re-add them to the `vibrancyEffectView.contentView`
  71. privateVisualEffectView?.contentView.subviews.forEach {
  72. vibrancyEffectView.contentView.addSubview($0)
  73. }
  74. blurEffectView.contentView.addSubview(vibrancyEffectView)
  75. } else {
  76. // If privateVisualEffectView is not `nil`, re-add them back to the subviews of original UI element.
  77. privateVisualEffectView?.contentView.subviews.forEach {
  78. blurableView.addSubview($0)
  79. }
  80. }
  81. blurableView.insertSubview(blurEffectView, at: 0)
  82. }
  83. private func makeVisualEffectView(effect: UIVisualEffect, in view: UIView) -> UIVisualEffectView {
  84. let visualEffectView = PrivateVisualEffectView(effect: effect)
  85. visualEffectView.alpha = blurOpacity.isNaN ? 1.0 : blurOpacity
  86. if view.layer.cornerRadius > 0 {
  87. visualEffectView.layer.cornerRadius = view.layer.cornerRadius
  88. visualEffectView.clipsToBounds = true
  89. }
  90. visualEffectView.frame = view.bounds
  91. visualEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  92. return visualEffectView
  93. }
  94. }
  95. /// Private class of visual effect view used in `BlurDesignable` only
  96. private class PrivateVisualEffectView: UIVisualEffectView {
  97. }