Replace UINavigationController with custom navigation controller

This commit is contained in:
Shadowfacts 2020-12-17 22:27:23 -05:00
parent 314d8cf82c
commit abb80df9a7
6 changed files with 268 additions and 215 deletions

View File

@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import Combine
public protocol NavigationManagerDelegate: class { public protocol NavigationManagerDelegate: class {
func loadNonGeminiURL(_ url: URL) func loadNonGeminiURL(_ url: URL)
@ -19,6 +20,8 @@ public class NavigationManager: NSObject, ObservableObject {
@Published public var backStack = [URL]() @Published public var backStack = [URL]()
@Published public var forwardStack = [URL]() @Published public var forwardStack = [URL]()
public let navigationOperation = PassthroughSubject<Operation, Never>()
public var displayURL: String { public var displayURL: String {
var components = URLComponents(url: currentURL, resolvingAgainstBaseURL: false)! var components = URLComponents(url: currentURL, resolvingAgainstBaseURL: false)!
if components.port == 1965 { if components.port == 1965 {
@ -58,6 +61,8 @@ public class NavigationManager: NSObject, ObservableObject {
backStack.append(currentURL) backStack.append(currentURL)
currentURL = url currentURL = url
forwardStack = [] forwardStack = []
navigationOperation.send(.go)
} }
public func reload() { public func reload() {
@ -66,9 +71,7 @@ public class NavigationManager: NSObject, ObservableObject {
} }
@objc public func back() { @objc public func back() {
guard !backStack.isEmpty else { return } back(count: 1)
forwardStack.insert(currentURL, at: 0)
currentURL = backStack.removeLast()
} }
public func back(count: Int) { public func back(count: Int) {
@ -78,12 +81,12 @@ public class NavigationManager: NSObject, ObservableObject {
forwardStack.insert(currentURL, at: 0) forwardStack.insert(currentURL, at: 0)
currentURL = removed.removeFirst() currentURL = removed.removeFirst()
forwardStack.insert(contentsOf: removed, at: 0) forwardStack.insert(contentsOf: removed, at: 0)
navigationOperation.send(.backward(count: count))
} }
@objc public func forward() { @objc public func forward() {
guard !forwardStack.isEmpty else { return } forward(count: 1)
backStack.append(currentURL)
currentURL = forwardStack.removeFirst()
} }
public func forward(count: Int) { public func forward(count: Int) {
@ -93,6 +96,14 @@ public class NavigationManager: NSObject, ObservableObject {
backStack.append(currentURL) backStack.append(currentURL)
currentURL = removed.removeLast() currentURL = removed.removeLast()
backStack.append(contentsOf: removed) backStack.append(contentsOf: removed)
navigationOperation.send(.forward(count: count))
} }
} }
public extension NavigationManager {
enum Operation {
case go, forward(count: Int), backward(count: Int)
}
}

View File

@ -2,21 +2,22 @@
// BrowserNavigationController.swift // BrowserNavigationController.swift
// Gemini-iOS // Gemini-iOS
// //
// Created by Shadowfacts on 12/16/20. // Created by Shadowfacts on 12/17/20.
// //
import UIKit import UIKit
import BrowserCore import BrowserCore
import Combine import Combine
class BrowserNavigationController: UINavigationController { class BrowserNavigationController: UIViewController {
let navigator: NavigationManager let navigator: NavigationManager
var poppedViewControllers = [UIViewController]() private var backBrowserVCs = [BrowserWebViewController]()
var skipResetPoppedOnNextPush = false private var forwardBrowserVCs = [BrowserWebViewController]()
private var currentBrowserVC: BrowserWebViewController!
private var interactivePushTransition: InteractivePushTransition! private var gestureState: GestureState?
private var cancellables = [AnyCancellable]() private var cancellables = [AnyCancellable]()
@ -24,74 +25,169 @@ class BrowserNavigationController: UINavigationController {
self.navigator = navigator self.navigator = navigator
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
navigator.$currentURL
.sink { [weak self] (newURL) in
self?.pushViewController(BrowserWebViewController(navigator: navigator, url: newURL), animated: true)
}
.store(in: &cancellables)
} }
required init?(coder aDecoder: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
interactivePushTransition = InteractivePushTransition(navigationController: self) view.backgroundColor = .systemBackground
currentBrowserVC = BrowserWebViewController(navigator: navigator, url: navigator.currentURL)
embedChild(currentBrowserVC)
navigator.navigationOperation
.sink(receiveValue: self.onNavigate)
.store(in: &cancellables)
view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognized)))
} }
override func popViewController(animated: Bool) -> UIViewController? { private func onNavigate(_ operation: NavigationManager.Operation) {
if let popped = super.popViewController(animated: animated) { let newVC: BrowserWebViewController
poppedViewControllers.insert(popped, at: 0)
return popped switch operation {
} else { case .go:
return nil backBrowserVCs.append(currentBrowserVC)
} newVC = BrowserWebViewController(navigator: navigator, url: navigator.currentURL)
case let .backward(count: count):
var removed = backBrowserVCs.suffix(count)
backBrowserVCs.removeLast(count)
forwardBrowserVCs.insert(currentBrowserVC, at: 0)
newVC = removed.removeFirst()
forwardBrowserVCs.insert(contentsOf: removed, at: 0)
case let .forward(count: count):
var removed = forwardBrowserVCs.prefix(count)
forwardBrowserVCs.removeFirst(count)
backBrowserVCs.append(currentBrowserVC)
newVC = removed.removeFirst()
backBrowserVCs.append(contentsOf: removed)
} }
override func popToRootViewController(animated: Bool) -> [UIViewController]? { currentBrowserVC.removeViewAndController()
if let popped = super.popToRootViewController(animated: animated) { currentBrowserVC = newVC
poppedViewControllers = popped embedChild(newVC)
return popped
} else {
return nil
}
} }
override func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? { private let startEdgeNavigationSwipeDistance: CGFloat = 75
if let popped = super.popToViewController(viewController, animated: animated) { private let finishEdgeNavigationVelocityThreshold: CGFloat = 500
poppedViewControllers.insert(contentsOf: popped, at: 0) private let edgeNavigationMaxDimmingAlpha: CGFloat = 0.35
return popped private let edgeNavigationParallaxFactor: CGFloat = 0.25
} else { private let totalEdgeNavigationTime: TimeInterval = 0.4
return nil
@objc private func panGestureRecognized(_ recognizer: UIPanGestureRecognizer) {
let location = recognizer.location(in: view)
let velocity = recognizer.velocity(in: view)
switch recognizer.state {
case .began:
if location.x < startEdgeNavigationSwipeDistance && velocity.x > 0 && navigator.backStack.count > 0 {
let older = backBrowserVCs.last ?? BrowserWebViewController(navigator: navigator, url: navigator.backStack.last!)
embedChild(older)
older.view.layer.zPosition = -2
older.view.transform = CGAffineTransform(translationX: -1 * edgeNavigationParallaxFactor * view.bounds.width, y: 0)
let dimmingView = UIView()
dimmingView.translatesAutoresizingMaskIntoConstraints = false
dimmingView.backgroundColor = .black
dimmingView.layer.zPosition = -1
dimmingView.alpha = edgeNavigationMaxDimmingAlpha
view.embedSubview(dimmingView)
let animator = UIViewPropertyAnimator(duration: totalEdgeNavigationTime, curve: .easeInOut) {
dimmingView.alpha = 0
older.view.transform = .identity
self.currentBrowserVC.view.transform = CGAffineTransform(translationX: self.view.bounds.width, y: 0)
} }
animator.addCompletion { (position) in
dimmingView.removeFromSuperview()
older.view.transform = .identity
older.view.layer.zPosition = 0
older.removeViewAndController()
self.currentBrowserVC.view.transform = .identity
if position == .end {
self.navigator.back()
}
}
gestureState = .backwards(animator)
} else if location.x > view.bounds.width - startEdgeNavigationSwipeDistance && velocity.x < 0 && navigator.forwardStack.count > 0 {
let newer = forwardBrowserVCs.first ?? BrowserWebViewController(navigator: navigator, url: navigator.backStack.first!)
embedChild(newer)
newer.view.transform = CGAffineTransform(translationX: view.bounds.width, y: 0)
newer.view.layer.zPosition = 2
let dimmingView = UIView()
dimmingView.translatesAutoresizingMaskIntoConstraints = false
dimmingView.backgroundColor = .black
dimmingView.layer.zPosition = 1
dimmingView.alpha = 0
view.embedSubview(dimmingView)
let animator = UIViewPropertyAnimator(duration: totalEdgeNavigationTime, curve: .easeInOut) {
dimmingView.alpha = self.edgeNavigationMaxDimmingAlpha
newer.view.transform = .identity
self.currentBrowserVC.view.transform = CGAffineTransform(translationX: -1 * self.edgeNavigationParallaxFactor * self.view.bounds.width, y: 0)
}
animator.addCompletion { (position) in
dimmingView.removeFromSuperview()
newer.removeViewAndController()
newer.view.layer.zPosition = 0
newer.view.transform = .identity
self.currentBrowserVC.view.transform = .identity
if position == .end {
self.navigator.forward()
}
}
gestureState = .forwards(animator)
} }
override func pushViewController(_ viewController: UIViewController, animated: Bool) { case .changed:
if skipResetPoppedOnNextPush { let translation = recognizer.translation(in: view)
skipResetPoppedOnNextPush = false
} else { switch gestureState {
self.poppedViewControllers = [] case let .backwards(animator):
} animator.fractionComplete = translation.x / view.bounds.width
super.pushViewController(viewController, animated: animated) case let .forwards(animator):
animator.fractionComplete = abs(translation.x) / view.bounds.width
case nil:
break
} }
func onWillShow() { case .ended, .cancelled:
self.transitionCoordinator?.notifyWhenInteractionChanges({ (context) in switch gestureState {
if context.isCancelled { case let .backwards(animator):
if self.interactivePushTransition.interactive { let shouldComplete = location.x > view.bounds.width / 2 || velocity.x > finishEdgeNavigationVelocityThreshold
// when an interactive push gesture is cancelled, make sure to adding the VC that was being pushed back onto the popped stack so it doesn't disappear animator.isReversed = !shouldComplete
self.poppedViewControllers.insert(self.interactivePushTransition.pushingViewController!, at: 0) animator.startAnimation()
} else { case let .forwards(animator):
// when an interactive pop gesture is cancelled (i.e. the user lifts their finger before it triggers), let shouldComplete = location.x < view.bounds.width / 2 || velocity.x < -finishEdgeNavigationVelocityThreshold
// the popViewController(animated:) method has already been called so the VC has already been added to the popped stack animator.isReversed = !shouldComplete
// so we make sure to remove it, otherwise there could be duplicate VCs on the navigation stasck animator.startAnimation()
self.poppedViewControllers.remove(at: 0) case nil:
break
} }
gestureState = nil
default:
return
} }
})
} }
} }
extension BrowserNavigationController {
enum GestureState {
case backwards(UIViewPropertyAnimator)
case forwards(UIViewPropertyAnimator)
}
}

View File

@ -63,6 +63,8 @@ class BrowserWebViewController: UIViewController {
view.backgroundColor = .systemBackground view.backgroundColor = .systemBackground
webView = WKWebView() webView = WKWebView()
webView.backgroundColor = .systemBackground
webView.isOpaque = false
webView.navigationDelegate = self webView.navigationDelegate = self
webView.translatesAutoresizingMaskIntoConstraints = false webView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(webView) view.addSubview(webView)

View File

@ -1,142 +0,0 @@
//
// InteractivePushTransition.swift
// Gemini-iOS
//
// Created by Shadowfacts on 12/16/20.
//
import UIKit
/// Allows interactively moving forward through the navigation stack after popping
/// Based on https://github.com/NSExceptional/TBInteractivePushTransition
class InteractivePushTransition: UIPercentDrivenInteractiveTransition {
fileprivate let minimumPushVelocityThreshold: CGFloat = 700
fileprivate let minimumPushDistanceThreshold: CGFloat = 0.5
fileprivate let pushAnimationDuration: TimeInterval = 0.35
private(set) weak var navigationController: BrowserNavigationController!
private(set) weak var interactivePushGestureRecognizer: UIScreenEdgePanGestureRecognizer!
private(set) var interactive = false
private(set) weak var pushingViewController: UIViewController?
init(navigationController: BrowserNavigationController) {
super.init()
self.navigationController = navigationController
let interactivePushGestureRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleSwipeForward(_:)))
self.interactivePushGestureRecognizer = interactivePushGestureRecognizer
navigationController.delegate = self
interactivePushGestureRecognizer.edges = .right
interactivePushGestureRecognizer.require(toFail: navigationController.interactivePopGestureRecognizer!)
navigationController.view.addGestureRecognizer(interactivePushGestureRecognizer)
let trackpadGestureRecognizer = TrackpadScrollGestureRecognizer(target: self, action: #selector(handleSwipeForward(_:)))
trackpadGestureRecognizer.require(toFail: navigationController.interactivePopGestureRecognizer!)
navigationController.view.addGestureRecognizer(trackpadGestureRecognizer)
}
@objc func handleSwipeForward(_ recognizer: UIPanGestureRecognizer) {
interactive = true
let velocity = recognizer.velocity(in: recognizer.view)
let translation = recognizer.translation(in: recognizer.view)
let dx = -translation.x / navigationController.view.bounds.width
let vx = -velocity.x
switch recognizer.state {
case .began:
if let viewController = navigationController.poppedViewControllers.first {
pushingViewController = viewController
navigationController.poppedViewControllers.removeFirst()
navigationController.skipResetPoppedOnNextPush = true
navigationController.pushViewController(viewController, animated: true)
} else {
interactive = false
}
case .changed:
update(dx)
case .ended:
if (dx > minimumPushDistanceThreshold || vx > minimumPushVelocityThreshold) {
finish()
} else {
cancel()
}
interactive = false
default:
cancel()
interactive = false
}
}
}
extension InteractivePushTransition: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
self.navigationController.onWillShow()
}
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if operation == .push, interactive {
return self
}
return nil
}
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
if interactive {
return self
}
return nil
}
}
extension InteractivePushTransition: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return pushAnimationDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let toView = transitionContext.view(forKey: .to)!
let fromView = transitionContext.view(forKey: .from)!
let dimmingView = UIView()
dimmingView.backgroundColor = .black
dimmingView.alpha = 0
dimmingView.frame = fromView.frame
transitionContext.containerView.addSubview(dimmingView)
transitionContext.containerView.addSubview(toView)
// modify frame of presented view to go from x = <screen width> to x = 0
var frame = fromView.frame
frame.origin.x = frame.width
toView.frame = frame
frame.origin.x = 0
// we want it linear while interactive, but we want the "ease out" animation
// if the user flicks the screen ahrd enough to finish the transition without interaction
let options = interactive ? UIView.AnimationOptions.curveLinear : .curveEaseOut
let duration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, delay: 0, options: options, animations: {
// these magic numbers scientifically determined by repeatedly adjusting and comparing to the system animation
let translationDistance = -frame.size.width * 0.3
fromView.transform = CGAffineTransform(translationX: translationDistance, y: 0)
toView.frame = frame
dimmingView.alpha = 0.075
}) { (finished) in
fromView.transform = .identity
dimmingView.removeFromSuperview()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}

View File

@ -0,0 +1,86 @@
//
// UIViewController+Children.swift
// Gemini-iOS
//
// Created by Shadowfacts on 12/17/20.
//
import UIKit
// Based on MVCTodo by Dave DeLong: https://github.com/davedelong/MVCTodo/blob/841649dd6aa31bacda3ad7ef9a9a836f66281e50/MVCTodo/Extensions/UIViewController.swift
extension UIViewController {
func embedChild(_ newChild: UIViewController, in container: UIView? = nil) {
// if the view controller is already a child of something else, remove it
if let oldParent = newChild.parent, oldParent != self {
newChild.beginAppearanceTransition(false, animated: false)
newChild.willMove(toParent: nil)
newChild.removeFromParent()
if newChild.viewIfLoaded?.superview != nil {
newChild.viewIfLoaded?.removeFromSuperview()
}
newChild.endAppearanceTransition()
}
// since .view returns an IUO, by default the type of this is "UIView?"
// explicitly type the variable because We Know Better
var targetContainer: UIView = container ?? self.view
if !targetContainer.isContainedWithin(view) {
targetContainer = view
}
// add the view controller as a child
if newChild.parent != self {
newChild.beginAppearanceTransition(true, animated: false)
addChild(newChild)
newChild.didMove(toParent: self)
targetContainer.embedSubview(newChild.view)
newChild.endAppearanceTransition()
} else {
// the view controller is already a child
// make sure it's in the right view
// we don't do the appearance transition stuff here,
// because the vc is already a child, so *presumably*
// that transition stuff has already appened
targetContainer.embedSubview(newChild.view)
}
}
func removeViewAndController() {
view.removeFromSuperview()
removeFromParent()
}
}
// Based on MVCTodo by Dave DeLong: https://github.com/davedelong/MVCTodo/blob/841649dd6aa31bacda3ad7ef9a9a836f66281e50/MVCTodo/Extensions/UIView.swift
extension UIView {
func embedSubview(_ subview: UIView) {
if subview.superview == self { return }
if subview.superview != nil {
subview.removeFromSuperview()
}
subview.frame = bounds
addSubview(subview)
NSLayoutConstraint.activate([
subview.leadingAnchor.constraint(equalTo: leadingAnchor),
subview.trailingAnchor.constraint(equalTo: trailingAnchor),
subview.topAnchor.constraint(equalTo: topAnchor),
subview.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
func isContainedWithin(_ other: UIView) -> Bool {
var current: UIView? = self
while let proposedView = current {
if proposedView == other { return true }
current = proposedView.superview
}
return false
}
}

View File

@ -44,10 +44,10 @@
D688F590258AC814003A0A73 /* HTMLEntities in Frameworks */ = {isa = PBXBuildFile; productRef = D688F58F258AC814003A0A73 /* HTMLEntities */; }; D688F590258AC814003A0A73 /* HTMLEntities in Frameworks */ = {isa = PBXBuildFile; productRef = D688F58F258AC814003A0A73 /* HTMLEntities */; };
D688F599258ACAAE003A0A73 /* BrowserWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D688F598258ACAAE003A0A73 /* BrowserWebViewController.swift */; }; D688F599258ACAAE003A0A73 /* BrowserWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D688F598258ACAAE003A0A73 /* BrowserWebViewController.swift */; };
D688F5FF258ACE6B003A0A73 /* browser.css in Resources */ = {isa = PBXBuildFile; fileRef = D688F5FE258ACE6B003A0A73 /* browser.css */; }; D688F5FF258ACE6B003A0A73 /* browser.css in Resources */ = {isa = PBXBuildFile; fileRef = D688F5FE258ACE6B003A0A73 /* browser.css */; };
D688F621258B0811003A0A73 /* BrowserNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D688F620258B0811003A0A73 /* BrowserNavigationController.swift */; };
D688F62A258B0833003A0A73 /* InteractivePushTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D688F629258B0833003A0A73 /* InteractivePushTransition.swift */; };
D688F633258B09BB003A0A73 /* TrackpadScrollGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D688F632258B09BB003A0A73 /* TrackpadScrollGestureRecognizer.swift */; }; D688F633258B09BB003A0A73 /* TrackpadScrollGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D688F632258B09BB003A0A73 /* TrackpadScrollGestureRecognizer.swift */; };
D688F64A258C17F3003A0A73 /* SymbolCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D688F649258C17F3003A0A73 /* SymbolCache.swift */; }; D688F64A258C17F3003A0A73 /* SymbolCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D688F649258C17F3003A0A73 /* SymbolCache.swift */; };
D688F65A258C2256003A0A73 /* BrowserNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D688F659258C2256003A0A73 /* BrowserNavigationController.swift */; };
D688F663258C2479003A0A73 /* UIViewController+Children.swift in Sources */ = {isa = PBXBuildFile; fileRef = D688F662258C2479003A0A73 /* UIViewController+Children.swift */; };
D691A64E25217C6F00348C4B /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A64D25217C6F00348C4B /* Preferences.swift */; }; D691A64E25217C6F00348C4B /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A64D25217C6F00348C4B /* Preferences.swift */; };
D691A66725217FD800348C4B /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A66625217FD800348C4B /* PreferencesView.swift */; }; D691A66725217FD800348C4B /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A66625217FD800348C4B /* PreferencesView.swift */; };
D691A68725223A4700348C4B /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A68625223A4600348C4B /* NavigationBar.swift */; }; D691A68725223A4700348C4B /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A68625223A4600348C4B /* NavigationBar.swift */; };
@ -309,10 +309,10 @@
D688F585258AC738003A0A73 /* GeminiHTMLRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiHTMLRenderer.swift; sourceTree = "<group>"; }; D688F585258AC738003A0A73 /* GeminiHTMLRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiHTMLRenderer.swift; sourceTree = "<group>"; };
D688F598258ACAAE003A0A73 /* BrowserWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserWebViewController.swift; sourceTree = "<group>"; }; D688F598258ACAAE003A0A73 /* BrowserWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserWebViewController.swift; sourceTree = "<group>"; };
D688F5FE258ACE6B003A0A73 /* browser.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = browser.css; sourceTree = "<group>"; }; D688F5FE258ACE6B003A0A73 /* browser.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = browser.css; sourceTree = "<group>"; };
D688F620258B0811003A0A73 /* BrowserNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserNavigationController.swift; sourceTree = "<group>"; };
D688F629258B0833003A0A73 /* InteractivePushTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractivePushTransition.swift; sourceTree = "<group>"; };
D688F632258B09BB003A0A73 /* TrackpadScrollGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackpadScrollGestureRecognizer.swift; sourceTree = "<group>"; }; D688F632258B09BB003A0A73 /* TrackpadScrollGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackpadScrollGestureRecognizer.swift; sourceTree = "<group>"; };
D688F649258C17F3003A0A73 /* SymbolCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolCache.swift; sourceTree = "<group>"; }; D688F649258C17F3003A0A73 /* SymbolCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolCache.swift; sourceTree = "<group>"; };
D688F659258C2256003A0A73 /* BrowserNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserNavigationController.swift; sourceTree = "<group>"; };
D688F662258C2479003A0A73 /* UIViewController+Children.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Children.swift"; sourceTree = "<group>"; };
D691A64D25217C6F00348C4B /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; }; D691A64D25217C6F00348C4B /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
D691A66625217FD800348C4B /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = "<group>"; }; D691A66625217FD800348C4B /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = "<group>"; };
D691A6762522382E00348C4B /* BrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserViewController.swift; sourceTree = "<group>"; }; D691A6762522382E00348C4B /* BrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserViewController.swift; sourceTree = "<group>"; };
@ -581,10 +581,10 @@
D6E152A424BFFDF500FDF9D3 /* AppDelegate.swift */, D6E152A424BFFDF500FDF9D3 /* AppDelegate.swift */,
D6E152A624BFFDF500FDF9D3 /* SceneDelegate.swift */, D6E152A624BFFDF500FDF9D3 /* SceneDelegate.swift */,
D688F649258C17F3003A0A73 /* SymbolCache.swift */, D688F649258C17F3003A0A73 /* SymbolCache.swift */,
D688F620258B0811003A0A73 /* BrowserNavigationController.swift */, D688F659258C2256003A0A73 /* BrowserNavigationController.swift */,
D688F629258B0833003A0A73 /* InteractivePushTransition.swift */,
D688F632258B09BB003A0A73 /* TrackpadScrollGestureRecognizer.swift */,
D688F598258ACAAE003A0A73 /* BrowserWebViewController.swift */, D688F598258ACAAE003A0A73 /* BrowserWebViewController.swift */,
D688F632258B09BB003A0A73 /* TrackpadScrollGestureRecognizer.swift */,
D688F662258C2479003A0A73 /* UIViewController+Children.swift */,
D691A6762522382E00348C4B /* BrowserViewController.swift */, D691A6762522382E00348C4B /* BrowserViewController.swift */,
D6E152A824BFFDF500FDF9D3 /* ContentView.swift */, D6E152A824BFFDF500FDF9D3 /* ContentView.swift */,
D691A68625223A4600348C4B /* NavigationBar.swift */, D691A68625223A4600348C4B /* NavigationBar.swift */,
@ -1103,9 +1103,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D688F621258B0811003A0A73 /* BrowserNavigationController.swift in Sources */,
D691A66725217FD800348C4B /* PreferencesView.swift in Sources */, D691A66725217FD800348C4B /* PreferencesView.swift in Sources */,
D688F62A258B0833003A0A73 /* InteractivePushTransition.swift in Sources */,
D688F599258ACAAE003A0A73 /* BrowserWebViewController.swift in Sources */, D688F599258ACAAE003A0A73 /* BrowserWebViewController.swift in Sources */,
D688F633258B09BB003A0A73 /* TrackpadScrollGestureRecognizer.swift in Sources */, D688F633258B09BB003A0A73 /* TrackpadScrollGestureRecognizer.swift in Sources */,
D6E152A524BFFDF500FDF9D3 /* AppDelegate.swift in Sources */, D6E152A524BFFDF500FDF9D3 /* AppDelegate.swift in Sources */,
@ -1113,7 +1111,9 @@
D6E152A724BFFDF500FDF9D3 /* SceneDelegate.swift in Sources */, D6E152A724BFFDF500FDF9D3 /* SceneDelegate.swift in Sources */,
D688F64A258C17F3003A0A73 /* SymbolCache.swift in Sources */, D688F64A258C17F3003A0A73 /* SymbolCache.swift in Sources */,
D62BCEE2252553620031D894 /* ActivityView.swift in Sources */, D62BCEE2252553620031D894 /* ActivityView.swift in Sources */,
D688F65A258C2256003A0A73 /* BrowserNavigationController.swift in Sources */,
D691A68725223A4700348C4B /* NavigationBar.swift in Sources */, D691A68725223A4700348C4B /* NavigationBar.swift in Sources */,
D688F663258C2479003A0A73 /* UIViewController+Children.swift in Sources */,
D691A64E25217C6F00348C4B /* Preferences.swift in Sources */, D691A64E25217C6F00348C4B /* Preferences.swift in Sources */,
D6E152A924BFFDF500FDF9D3 /* ContentView.swift in Sources */, D6E152A924BFFDF500FDF9D3 /* ContentView.swift in Sources */,
); );