130 lines
5.7 KiB
Swift
130 lines
5.7 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 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
|
|
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.isHidden = viewController.prefersStatusBarHidden
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
}
|