|
//
// NavigationBar.swift
// PaiaiUIKit
//
// Created by ffib on 2019/4/23.
// Copyright © 2019 FFIB. All rights reserved.
//
import UIKit
class NavigationBar: UINavigationBar {
private var currBgImage: UIImage?
private var targetBgImage: UIImage?
private var originShadowImage: UIImage?
private var hasChangedBgImage: Bool = false
private var hasChangedShadow: Bool = false
private var titleView: UIView?
var needsInteractive: Bool {
return hasChangedBgImage || hasChangedShadow
}
var bounce: Bounce = .none
var isPush: Bool = true
override var shadowImage: UIImage? {
didSet {
originShadowImage = oldValue
hasChangedShadow = true
}
}
lazy var fakeView: UIVisualEffectView = {
let v = UIVisualEffectView(effect: UIBlurEffect(style: .light))
v.frame = getNavigationBarBounds()
v.isUserInteractionEnabled = false
v.autoresizingMask = [.flexibleWidth, .flexibleHeight]
return v
}()
lazy var backgroundImageView: UIImageView = {
let v = UIImageView()
v.isUserInteractionEnabled = false
v.frame = getNavigationBarBounds().offsetBy(dx: bounds.width, dy: 0)
return v
}()
lazy var shadowImageView: UIImageView = {
let v = UIImageView()
v.frame = CGRect(x: 0, y: bounds.height, width: bounds.width, height: 0.5)
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setBackgroundImage(_ backgroundImage: UIImage?, for barMetrics: UIBarMetrics) {
currBgImage = self.backgroundImage(for: .default)
targetBgImage = backgroundImage
hasChangedBgImage = true
}
override func setBackgroundImage(_ backgroundImage: UIImage?, for barPosition: UIBarPosition, barMetrics: UIBarMetrics) {
currBgImage = self.backgroundImage(for: .default)
targetBgImage = backgroundImage
hasChangedBgImage = true
}
func setBackgroundImage() {
super.setBackgroundImage(targetBgImage, for: .any, barMetrics: .default)
}
func getBarBackground() -> UIView? {
return value(forKeyPath: "_backgroundView") as? UIView
}
func getContentView() -> UIView? {
for v in subviews {
if let ContentClass = NSClassFromString("_UINavigationBarContentView"), v.isKind(of: ContentClass) {
return v
}
}
return nil
}
func getShadowView() -> UIView? {
guard let barBackground = getBarBackground() else { return nil }
for v in barBackground.subviews {
if (v.bounds.height == 0.5) {
return v
}
}
return nil
}
func getNavigationBarBounds() -> CGRect {
let statusHeight = UIApplication.shared.statusBarFrame.height
return CGRect(x: 0, y: -statusHeight, width: bounds.width, height: bounds.height + statusHeight)
}
override func value(forUndefinedKey key: String) -> Any? {
return nil
}
}
/// NavigationBar transition
extension NavigationBar {
func constructViewHierarchy() {
setupShadowView()
setupBackgroundImageView()
guard let barBackground = getBarBackground() else { return }
insertSubview(shadowImageView, aboveSubview: barBackground)
insertSubview(backgroundImageView, aboveSubview: barBackground)
insertSubview(fakeView, belowSubview: backgroundImageView)
layoutIfNeeded()
}
private func setupShadowView() {
if let image = originShadowImage, image.size == CGSize.zero {
shadowImageView.image = image
} else {
shadowImageView.backgroundColor = UIColor(red: 0, green: 0, blue: 0,
alpha: 77.0 / 255)
}
shadowImageView.alpha = originShadowImage == nil ? 1 : 0
getShadowView()?.isHidden = true
}
private func setupBackgroundImageView() {
if isPush {
fakeView.alpha = 0
backgroundImageView.image = targetBgImage
} else {
setBackgroundImage()
fakeView.alpha = 1
backgroundImageView.image = currBgImage
}
}
/// interactivePopGestureRecognizer
func transitionAnimationWithPercent(_ percent: CGFloat) {
switch bounce {
case .forward, .none:
transitionShadowAnimationWithPercent(percent)
transitionBackgroundAnimationWithPercent(percent)
break
case .backward:
transitionShadowAnimationWithPercent(1 - percent)
transitionBackgroundAnimationWithPercent(1 - percent)
break
}
}
func transitionAnimation() {
transitionShadowAnimation()
transitionBackgroundAnimation()
}
private func transitionShadowAnimation() {
guard hasChangedShadow else { return }
shadowImageView.alpha = originShadowImage == nil ? 0 : 1
// if originShadowImage == nil {
// shadowImageView.alpha = isInteractive ? (1 - percent) * 1 : 0
// } else {
// shadowImageView.alpha = isInteractive ? percent * 1 : 1
// }
}
private func transitionBackgroundAnimation() {
guard hasChangedBgImage else { return }
if isPush {
fakeView.alpha = 1
backgroundImageView.frame.origin.x = 0
} else {
fakeView.alpha = 0
backgroundImageView.frame.origin.x = bounds.width
}
}
private func transitionShadowAnimationWithPercent(_ percent: CGFloat) {
guard hasChangedShadow else { return }
shadowImageView.alpha = originShadowImage == nil ? (1 - percent) * 1 : percent * 1
}
private func transitionBackgroundAnimationWithPercent(_ percent: CGFloat) {
guard hasChangedBgImage else { return }
fakeView.alpha = (1 - percent) * 1
backgroundImageView.frame.origin.x = percent * bounds.width
}
func clear() {
if isPush && hasChangedBgImage { setBackgroundImage() }
if !isPush && bounce == .backward && hasChangedBgImage {
targetBgImage = currBgImage
setBackgroundImage()
}
getShadowView()?.isHidden = false
fakeView.removeFromSuperview()
shadowImageView.removeFromSuperview()
backgroundImageView.removeFromSuperview()
currBgImage = nil
targetBgImage = nil
hasChangedBgImage = false
hasChangedShadow = false
bounce = .none
}
}
extension NavigationBar {
enum Bounce {
case none
case backward
case forward
}
}
|