// // 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 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 } } }