frenzy-ios/Reader/Screens/AppNavigationController.swift

154 lines
6.5 KiB
Swift

//
// AppNavigationController.swift
// Reader
//
// Created by Shadowfacts on 1/10/22.
//
import UIKit
class AppNavigationController: UINavigationController, UINavigationControllerDelegate {
private var statusBarBlockingView: UIView!
static let panRecognizerName = "AppNavPanRecognizer"
override var childForStatusBarHidden: UIViewController? {
topViewController
}
override var childForStatusBarStyle: UIViewController? {
topViewController
}
override func viewDidLoad() {
super.viewDidLoad()
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
navigationBar.scrollEdgeAppearance = appearance
interactivePopGestureRecognizer?.isEnabled = false
let recognizer = UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognized))
recognizer.allowedScrollTypesMask = .continuous
recognizer.name = AppNavigationController.panRecognizerName
recognizer.delegate = self
view.addGestureRecognizer(recognizer)
isNavigationBarHidden = true
statusBarBlockingView = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
statusBarBlockingView.translatesAutoresizingMaskIntoConstraints = false
statusBarBlockingView.layer.zPosition = 101
view.addSubview(statusBarBlockingView)
NSLayoutConstraint.activate([
statusBarBlockingView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
statusBarBlockingView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
statusBarBlockingView.topAnchor.constraint(equalTo: view.topAnchor),
statusBarBlockingView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
])
delegate = self
}
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
statusBarBlockingView.layer.opacity = viewController.prefersStatusBarHidden ? 0 : 1
}
override func setNeedsStatusBarAppearanceUpdate() {
super.setNeedsStatusBarAppearanceUpdate()
statusBarBlockingView?.layer.opacity = childForStatusBarHidden!.prefersStatusBarHidden ? 0 : 1
}
private var poppingViewController: UIViewController?
private var prevNavBarHidden = false
private var dimmingView: UIView = {
let v = UIView()
v.backgroundColor = .black
return v
}()
@objc private func panGestureRecognized(_ recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translation(in: view)
let translationProgress = max(0, translation.x) / view.bounds.width
switch recognizer.state {
case .began:
guard viewControllers.count > 1 else {
break
}
prevNavBarHidden = isNavigationBarHidden
poppingViewController = popViewController(animated: false)
view.addSubview(poppingViewController!.view)
poppingViewController!.view.transform = CGAffineTransform(translationX: max(0, translation.x), y: 0)
poppingViewController!.view.layer.zPosition = 100
dimmingView.frame = view.bounds
dimmingView.layer.opacity = Float(1 - translationProgress) * 0.075
dimmingView.layer.zPosition = 99
view.addSubview(dimmingView)
// changing the transform directly on topViewController.view doesn't work for some reason, have to go 2 superviews up
topViewController!.view.superview?.superview?.transform = CGAffineTransform(translationX: (1 - translationProgress) * -0.3 * view.bounds.width, y: 0)
case .changed:
guard let poppingViewController = poppingViewController else {
break
}
poppingViewController.view.transform = CGAffineTransform(translationX: max(0, translation.x), y: 0)
dimmingView.layer.opacity = Float(1 - max(0, translation.x) / view.bounds.width) * 0.075
topViewController!.view.superview?.superview?.transform = CGAffineTransform(translationX: (1 - translationProgress) * -0.3 * view.bounds.width, y: 0)
case .ended:
guard let poppingViewController = poppingViewController else {
break
}
let velocity = recognizer.velocity(in: view)
let shouldComplete = translation.x >= view.bounds.width / 2 || velocity.x >= 500
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut) {
if shouldComplete {
poppingViewController.view.transform = CGAffineTransform(translationX: self.view.bounds.width, y: 0)
self.topViewController!.view.superview?.superview?.transform = .identity
} else {
poppingViewController.view.transform = .identity
self.topViewController!.view.superview?.superview?.transform = CGAffineTransform(translationX: -0.3 * self.view.bounds.width, y: 0)
}
self.dimmingView.layer.opacity = 0
} completion: { _ in
self.topViewController!.view.superview?.superview?.transform = .identity
if shouldComplete {
poppingViewController.beginAppearanceTransition(false, animated: true)
poppingViewController.willMove(toParent: nil)
poppingViewController.removeFromParent()
poppingViewController.view.removeFromSuperview()
poppingViewController.endAppearanceTransition()
} else {
self.pushViewController(poppingViewController, animated: false)
self.isNavigationBarHidden = self.prevNavBarHidden
}
poppingViewController.view.layer.zPosition = 0
self.poppingViewController = nil
self.dimmingView.removeFromSuperview()
}
default:
break
}
}
}
extension AppNavigationController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if String(describing: type(of: otherGestureRecognizer)) == "_UISwipeActionPanGestureRecognizer" {
return true
} else {
return false
}
}
}