From 220fbf7b7505d3e9361062af1d146df5a212b507 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Mon, 10 Jan 2022 22:11:14 -0500 Subject: [PATCH] Custom nav controller --- Reader.xcodeproj/project.pbxproj | 4 + Reader/SceneDelegate.swift | 2 +- Reader/Screens/AppNavigationController.swift | 126 +++++++++++++++++++ Reader/Screens/Read/ReadViewController.swift | 12 -- 4 files changed, 131 insertions(+), 13 deletions(-) create mode 100644 Reader/Screens/AppNavigationController.swift diff --git a/Reader.xcodeproj/project.pbxproj b/Reader.xcodeproj/project.pbxproj index b3fdc23..5f23ad2 100644 --- a/Reader.xcodeproj/project.pbxproj +++ b/Reader.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ D6E2436E278BD8160005E546 /* ReadViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2436D278BD8160005E546 /* ReadViewController.swift */; }; D6E24371278BE1250005E546 /* HTMLEntities in Frameworks */ = {isa = PBXBuildFile; productRef = D6E24370278BE1250005E546 /* HTMLEntities */; }; D6E24373278BE2B80005E546 /* read.css in Resources */ = {isa = PBXBuildFile; fileRef = D6E24372278BE2B80005E546 /* read.css */; }; + D6EB531D278C89C300AD2E61 /* AppNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EB531C278C89C300AD2E61 /* AppNavigationController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -129,6 +130,7 @@ D6E2436A278BB1880005E546 /* HomeCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCollectionViewCell.swift; sourceTree = ""; }; D6E2436D278BD8160005E546 /* ReadViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadViewController.swift; sourceTree = ""; }; D6E24372278BE2B80005E546 /* read.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = read.css; sourceTree = ""; }; + D6EB531C278C89C300AD2E61 /* AppNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNavigationController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -169,6 +171,7 @@ D65B18AF2750468B004A9448 /* Screens */ = { isa = PBXGroup; children = ( + D6EB531C278C89C300AD2E61 /* AppNavigationController.swift */, D65B18BF2750533E004A9448 /* Home */, D65B18B027504691004A9448 /* Login */, D6E2434A278B455C0005E546 /* Items */, @@ -492,6 +495,7 @@ D6E2435F278B97240005E546 /* Group+CoreDataClass.swift in Sources */, D6E24369278BABB40005E546 /* UIColor+App.swift in Sources */, D6E2435D278B97240005E546 /* Item+CoreDataClass.swift in Sources */, + D6EB531D278C89C300AD2E61 /* AppNavigationController.swift in Sources */, D6E24360278B97240005E546 /* Group+CoreDataProperties.swift in Sources */, D6E2434C278B456A0005E546 /* ItemsViewController.swift in Sources */, D6E2435E278B97240005E546 /* Item+CoreDataProperties.swift in Sources */, diff --git a/Reader/SceneDelegate.swift b/Reader/SceneDelegate.swift index 9dd4449..c9840c5 100644 --- a/Reader/SceneDelegate.swift +++ b/Reader/SceneDelegate.swift @@ -67,7 +67,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { private func createAppUI() { let home = HomeViewController(fervorController: fervorController) - let nav = UINavigationController(rootViewController: home) + let nav = AppNavigationController(rootViewController: home) nav.navigationBar.prefersLargeTitles = true window!.rootViewController = nav diff --git a/Reader/Screens/AppNavigationController.swift b/Reader/Screens/AppNavigationController.swift new file mode 100644 index 0000000..19eea30 --- /dev/null +++ b/Reader/Screens/AppNavigationController.swift @@ -0,0 +1,126 @@ +// +// AppNavigationController.swift +// Reader +// +// Created by Shadowfacts on 1/10/22. +// + +import UIKit + +class AppNavigationController: UINavigationController, UINavigationControllerDelegate { + + private var statusBarBlockingView: UIView! + + 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 + 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 + } + } + +} diff --git a/Reader/Screens/Read/ReadViewController.swift b/Reader/Screens/Read/ReadViewController.swift index d20ccbd..e5a1edd 100644 --- a/Reader/Screens/Read/ReadViewController.swift +++ b/Reader/Screens/Read/ReadViewController.swift @@ -66,18 +66,6 @@ class ReadViewController: UIViewController { } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - navigationController?.hidesBarsOnSwipe = true - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - - navigationController?.hidesBarsOnSwipe = false - } - private func itemContentHTML() -> String? { guard let content = item.content else { return nil