No Description

NavigationBar.swift 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. //
  2. // NavigationBar.swift
  3. // PaiaiUIKit
  4. //
  5. // Created by ffib on 2019/4/23.
  6. // Copyright © 2019 FFIB. All rights reserved.
  7. //
  8. import UIKit
  9. class NavigationBar: UINavigationBar {
  10. private var currBgImage: UIImage?
  11. private var targetBgImage: UIImage?
  12. private var originShadowImage: UIImage?
  13. private var hasChangedBgImage: Bool = false
  14. private var hasChangedShadow: Bool = false
  15. private var titleView: UIView?
  16. var needsInteractive: Bool {
  17. return hasChangedBgImage || hasChangedShadow
  18. }
  19. var bounce: Bounce = .none
  20. var isPush: Bool = true
  21. override var shadowImage: UIImage? {
  22. didSet {
  23. originShadowImage = oldValue
  24. hasChangedShadow = true
  25. }
  26. }
  27. lazy var fakeView: UIVisualEffectView = {
  28. let v = UIVisualEffectView(effect: UIBlurEffect(style: .light))
  29. v.frame = getNavigationBarBounds()
  30. v.isUserInteractionEnabled = false
  31. v.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  32. return v
  33. }()
  34. lazy var backgroundImageView: UIImageView = {
  35. let v = UIImageView()
  36. v.isUserInteractionEnabled = false
  37. v.frame = getNavigationBarBounds().offsetBy(dx: bounds.width, dy: 0)
  38. return v
  39. }()
  40. lazy var shadowImageView: UIImageView = {
  41. let v = UIImageView()
  42. v.frame = CGRect(x: 0, y: bounds.height, width: bounds.width, height: 0.5)
  43. return v
  44. }()
  45. override init(frame: CGRect) {
  46. super.init(frame: frame)
  47. }
  48. required init?(coder aDecoder: NSCoder) {
  49. fatalError("init(coder:) has not been implemented")
  50. }
  51. override func setBackgroundImage(_ backgroundImage: UIImage?, for barMetrics: UIBarMetrics) {
  52. currBgImage = self.backgroundImage(for: .default)
  53. targetBgImage = backgroundImage
  54. hasChangedBgImage = true
  55. }
  56. override func setBackgroundImage(_ backgroundImage: UIImage?, for barPosition: UIBarPosition, barMetrics: UIBarMetrics) {
  57. currBgImage = self.backgroundImage(for: .default)
  58. targetBgImage = backgroundImage
  59. hasChangedBgImage = true
  60. }
  61. func setBackgroundImage() {
  62. super.setBackgroundImage(targetBgImage, for: .any, barMetrics: .default)
  63. }
  64. func getBarBackground() -> UIView? {
  65. return value(forKeyPath: "_backgroundView") as? UIView
  66. }
  67. func getContentView() -> UIView? {
  68. for v in subviews {
  69. if let ContentClass = NSClassFromString("_UINavigationBarContentView"), v.isKind(of: ContentClass) {
  70. return v
  71. }
  72. }
  73. return nil
  74. }
  75. func getShadowView() -> UIView? {
  76. guard let barBackground = getBarBackground() else { return nil }
  77. for v in barBackground.subviews {
  78. if (v.bounds.height == 0.5) {
  79. return v
  80. }
  81. }
  82. return nil
  83. }
  84. func getNavigationBarBounds() -> CGRect {
  85. let statusHeight = UIApplication.shared.statusBarFrame.height
  86. return CGRect(x: 0, y: -statusHeight, width: bounds.width, height: bounds.height + statusHeight)
  87. }
  88. override func value(forUndefinedKey key: String) -> Any? {
  89. return nil
  90. }
  91. }
  92. /// NavigationBar transition
  93. extension NavigationBar {
  94. func constructViewHierarchy() {
  95. setupShadowView()
  96. setupBackgroundImageView()
  97. guard let barBackground = getBarBackground() else { return }
  98. insertSubview(shadowImageView, aboveSubview: barBackground)
  99. insertSubview(backgroundImageView, aboveSubview: barBackground)
  100. insertSubview(fakeView, belowSubview: backgroundImageView)
  101. layoutIfNeeded()
  102. }
  103. private func setupShadowView() {
  104. if let image = originShadowImage, image.size == CGSize.zero {
  105. shadowImageView.image = image
  106. } else {
  107. shadowImageView.backgroundColor = UIColor(red: 0, green: 0, blue: 0,
  108. alpha: 77.0 / 255)
  109. }
  110. shadowImageView.alpha = originShadowImage == nil ? 1 : 0
  111. getShadowView()?.isHidden = true
  112. }
  113. private func setupBackgroundImageView() {
  114. if isPush {
  115. fakeView.alpha = 0
  116. backgroundImageView.image = targetBgImage
  117. } else {
  118. setBackgroundImage()
  119. fakeView.alpha = 1
  120. backgroundImageView.image = currBgImage
  121. }
  122. }
  123. /// interactivePopGestureRecognizer
  124. func transitionAnimationWithPercent(_ percent: CGFloat) {
  125. switch bounce {
  126. case .forward, .none:
  127. transitionShadowAnimationWithPercent(percent)
  128. transitionBackgroundAnimationWithPercent(percent)
  129. break
  130. case .backward:
  131. transitionShadowAnimationWithPercent(1 - percent)
  132. transitionBackgroundAnimationWithPercent(1 - percent)
  133. break
  134. }
  135. }
  136. func transitionAnimation() {
  137. transitionShadowAnimation()
  138. transitionBackgroundAnimation()
  139. }
  140. private func transitionShadowAnimation() {
  141. guard hasChangedShadow else { return }
  142. shadowImageView.alpha = originShadowImage == nil ? 0 : 1
  143. // if originShadowImage == nil {
  144. // shadowImageView.alpha = isInteractive ? (1 - percent) * 1 : 0
  145. // } else {
  146. // shadowImageView.alpha = isInteractive ? percent * 1 : 1
  147. // }
  148. }
  149. private func transitionBackgroundAnimation() {
  150. guard hasChangedBgImage else { return }
  151. if isPush {
  152. fakeView.alpha = 1
  153. backgroundImageView.frame.origin.x = 0
  154. } else {
  155. fakeView.alpha = 0
  156. backgroundImageView.frame.origin.x = bounds.width
  157. }
  158. }
  159. private func transitionShadowAnimationWithPercent(_ percent: CGFloat) {
  160. guard hasChangedShadow else { return }
  161. shadowImageView.alpha = originShadowImage == nil ? (1 - percent) * 1 : percent * 1
  162. }
  163. private func transitionBackgroundAnimationWithPercent(_ percent: CGFloat) {
  164. guard hasChangedBgImage else { return }
  165. fakeView.alpha = (1 - percent) * 1
  166. backgroundImageView.frame.origin.x = percent * bounds.width
  167. }
  168. func clear() {
  169. if isPush && hasChangedBgImage { setBackgroundImage() }
  170. if !isPush && bounce == .backward && hasChangedBgImage {
  171. targetBgImage = currBgImage
  172. setBackgroundImage()
  173. }
  174. getShadowView()?.isHidden = false
  175. fakeView.removeFromSuperview()
  176. shadowImageView.removeFromSuperview()
  177. backgroundImageView.removeFromSuperview()
  178. currBgImage = nil
  179. targetBgImage = nil
  180. hasChangedBgImage = false
  181. hasChangedShadow = false
  182. bounce = .none
  183. }
  184. }
  185. extension NavigationBar {
  186. enum Bounce {
  187. case none
  188. case backward
  189. case forward
  190. }
  191. }