diff --git a/BrowserCore/NavigationManager.swift b/BrowserCore/NavigationManager.swift index 7c81ccc..0ddc061 100644 --- a/BrowserCore/NavigationManager.swift +++ b/BrowserCore/NavigationManager.swift @@ -6,6 +6,7 @@ // import Foundation +import Combine public protocol NavigationManagerDelegate: class { func loadNonGeminiURL(_ url: URL) @@ -19,6 +20,8 @@ public class NavigationManager: NSObject, ObservableObject { @Published public var backStack = [URL]() @Published public var forwardStack = [URL]() + public let navigationOperation = PassthroughSubject() + public var displayURL: String { var components = URLComponents(url: currentURL, resolvingAgainstBaseURL: false)! if components.port == 1965 { @@ -58,6 +61,8 @@ public class NavigationManager: NSObject, ObservableObject { backStack.append(currentURL) currentURL = url forwardStack = [] + + navigationOperation.send(.go) } public func reload() { @@ -66,9 +71,7 @@ public class NavigationManager: NSObject, ObservableObject { } @objc public func back() { - guard !backStack.isEmpty else { return } - forwardStack.insert(currentURL, at: 0) - currentURL = backStack.removeLast() + back(count: 1) } public func back(count: Int) { @@ -78,12 +81,12 @@ public class NavigationManager: NSObject, ObservableObject { forwardStack.insert(currentURL, at: 0) currentURL = removed.removeFirst() forwardStack.insert(contentsOf: removed, at: 0) + + navigationOperation.send(.backward(count: count)) } @objc public func forward() { - guard !forwardStack.isEmpty else { return } - backStack.append(currentURL) - currentURL = forwardStack.removeFirst() + forward(count: 1) } public func forward(count: Int) { @@ -93,6 +96,14 @@ public class NavigationManager: NSObject, ObservableObject { backStack.append(currentURL) currentURL = removed.removeLast() backStack.append(contentsOf: removed) + + navigationOperation.send(.forward(count: count)) } } + +public extension NavigationManager { + enum Operation { + case go, forward(count: Int), backward(count: Int) + } +} diff --git a/Gemini-iOS/BrowserNavigationController.swift b/Gemini-iOS/BrowserNavigationController.swift index ae43239..439f48a 100644 --- a/Gemini-iOS/BrowserNavigationController.swift +++ b/Gemini-iOS/BrowserNavigationController.swift @@ -2,21 +2,22 @@ // BrowserNavigationController.swift // Gemini-iOS // -// Created by Shadowfacts on 12/16/20. +// Created by Shadowfacts on 12/17/20. // import UIKit import BrowserCore import Combine -class BrowserNavigationController: UINavigationController { +class BrowserNavigationController: UIViewController { let navigator: NavigationManager - var poppedViewControllers = [UIViewController]() - var skipResetPoppedOnNextPush = false + private var backBrowserVCs = [BrowserWebViewController]() + private var forwardBrowserVCs = [BrowserWebViewController]() + private var currentBrowserVC: BrowserWebViewController! - private var interactivePushTransition: InteractivePushTransition! + private var gestureState: GestureState? private var cancellables = [AnyCancellable]() @@ -24,74 +25,169 @@ class BrowserNavigationController: UINavigationController { self.navigator = navigator 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") } override func 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? { - if let popped = super.popViewController(animated: animated) { - poppedViewControllers.insert(popped, at: 0) - return popped - } else { - return nil + private func onNavigate(_ operation: NavigationManager.Operation) { + let newVC: BrowserWebViewController + + switch operation { + case .go: + 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) } + + currentBrowserVC.removeViewAndController() + currentBrowserVC = newVC + embedChild(newVC) } - override func popToRootViewController(animated: Bool) -> [UIViewController]? { - if let popped = super.popToRootViewController(animated: animated) { - poppedViewControllers = popped - return popped - } else { - return nil - } - } + private let startEdgeNavigationSwipeDistance: CGFloat = 75 + private let finishEdgeNavigationVelocityThreshold: CGFloat = 500 + private let edgeNavigationMaxDimmingAlpha: CGFloat = 0.35 + private let edgeNavigationParallaxFactor: CGFloat = 0.25 + private let totalEdgeNavigationTime: TimeInterval = 0.4 - override func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? { - if let popped = super.popToViewController(viewController, animated: animated) { - poppedViewControllers.insert(contentsOf: popped, at: 0) - return popped - } else { - return nil - } - } - - override func pushViewController(_ viewController: UIViewController, animated: Bool) { - if skipResetPoppedOnNextPush { - skipResetPoppedOnNextPush = false - } else { - self.poppedViewControllers = [] - } - super.pushViewController(viewController, animated: animated) - } - - func onWillShow() { - self.transitionCoordinator?.notifyWhenInteractionChanges({ (context) in - if context.isCancelled { - if self.interactivePushTransition.interactive { - // 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 - self.poppedViewControllers.insert(self.interactivePushTransition.pushingViewController!, at: 0) - } else { - // when an interactive pop gesture is cancelled (i.e. the user lifts their finger before it triggers), - // the popViewController(animated:) method has already been called so the VC has already been added to the popped stack - // so we make sure to remove it, otherwise there could be duplicate VCs on the navigation stasck - self.poppedViewControllers.remove(at: 0) + @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) } - }) + + case .changed: + let translation = recognizer.translation(in: view) + + switch gestureState { + case let .backwards(animator): + animator.fractionComplete = translation.x / view.bounds.width + case let .forwards(animator): + animator.fractionComplete = abs(translation.x) / view.bounds.width + case nil: + break + } + + case .ended, .cancelled: + switch gestureState { + case let .backwards(animator): + let shouldComplete = location.x > view.bounds.width / 2 || velocity.x > finishEdgeNavigationVelocityThreshold + animator.isReversed = !shouldComplete + animator.startAnimation() + case let .forwards(animator): + let shouldComplete = location.x < view.bounds.width / 2 || velocity.x < -finishEdgeNavigationVelocityThreshold + animator.isReversed = !shouldComplete + animator.startAnimation() + case nil: + break + } + + gestureState = nil + + default: + return + } + } + +} + +extension BrowserNavigationController { + enum GestureState { + case backwards(UIViewPropertyAnimator) + case forwards(UIViewPropertyAnimator) } - } diff --git a/Gemini-iOS/BrowserWebViewController.swift b/Gemini-iOS/BrowserWebViewController.swift index 314ffce..4582107 100644 --- a/Gemini-iOS/BrowserWebViewController.swift +++ b/Gemini-iOS/BrowserWebViewController.swift @@ -63,6 +63,8 @@ class BrowserWebViewController: UIViewController { view.backgroundColor = .systemBackground webView = WKWebView() + webView.backgroundColor = .systemBackground + webView.isOpaque = false webView.navigationDelegate = self webView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(webView) diff --git a/Gemini-iOS/InteractivePushTransition.swift b/Gemini-iOS/InteractivePushTransition.swift deleted file mode 100644 index 3fc9eec..0000000 --- a/Gemini-iOS/InteractivePushTransition.swift +++ /dev/null @@ -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 = 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) - } - } -} diff --git a/Gemini-iOS/UIViewController+Children.swift b/Gemini-iOS/UIViewController+Children.swift new file mode 100644 index 0000000..03d6bd4 --- /dev/null +++ b/Gemini-iOS/UIViewController+Children.swift @@ -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 + } +} + diff --git a/Gemini.xcodeproj/project.pbxproj b/Gemini.xcodeproj/project.pbxproj index df7d6ea..10d5d1f 100644 --- a/Gemini.xcodeproj/project.pbxproj +++ b/Gemini.xcodeproj/project.pbxproj @@ -44,10 +44,10 @@ D688F590258AC814003A0A73 /* HTMLEntities in Frameworks */ = {isa = PBXBuildFile; productRef = D688F58F258AC814003A0A73 /* HTMLEntities */; }; D688F599258ACAAE003A0A73 /* BrowserWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D688F598258ACAAE003A0A73 /* BrowserWebViewController.swift */; }; 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 */; }; 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 */; }; D691A66725217FD800348C4B /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A66625217FD800348C4B /* PreferencesView.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 = ""; }; D688F598258ACAAE003A0A73 /* BrowserWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserWebViewController.swift; sourceTree = ""; }; D688F5FE258ACE6B003A0A73 /* browser.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = browser.css; sourceTree = ""; }; - D688F620258B0811003A0A73 /* BrowserNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserNavigationController.swift; sourceTree = ""; }; - D688F629258B0833003A0A73 /* InteractivePushTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractivePushTransition.swift; sourceTree = ""; }; D688F632258B09BB003A0A73 /* TrackpadScrollGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackpadScrollGestureRecognizer.swift; sourceTree = ""; }; D688F649258C17F3003A0A73 /* SymbolCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolCache.swift; sourceTree = ""; }; + D688F659258C2256003A0A73 /* BrowserNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserNavigationController.swift; sourceTree = ""; }; + D688F662258C2479003A0A73 /* UIViewController+Children.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Children.swift"; sourceTree = ""; }; D691A64D25217C6F00348C4B /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; D691A66625217FD800348C4B /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = ""; }; D691A6762522382E00348C4B /* BrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserViewController.swift; sourceTree = ""; }; @@ -581,10 +581,10 @@ D6E152A424BFFDF500FDF9D3 /* AppDelegate.swift */, D6E152A624BFFDF500FDF9D3 /* SceneDelegate.swift */, D688F649258C17F3003A0A73 /* SymbolCache.swift */, - D688F620258B0811003A0A73 /* BrowserNavigationController.swift */, - D688F629258B0833003A0A73 /* InteractivePushTransition.swift */, - D688F632258B09BB003A0A73 /* TrackpadScrollGestureRecognizer.swift */, + D688F659258C2256003A0A73 /* BrowserNavigationController.swift */, D688F598258ACAAE003A0A73 /* BrowserWebViewController.swift */, + D688F632258B09BB003A0A73 /* TrackpadScrollGestureRecognizer.swift */, + D688F662258C2479003A0A73 /* UIViewController+Children.swift */, D691A6762522382E00348C4B /* BrowserViewController.swift */, D6E152A824BFFDF500FDF9D3 /* ContentView.swift */, D691A68625223A4600348C4B /* NavigationBar.swift */, @@ -1103,9 +1103,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D688F621258B0811003A0A73 /* BrowserNavigationController.swift in Sources */, D691A66725217FD800348C4B /* PreferencesView.swift in Sources */, - D688F62A258B0833003A0A73 /* InteractivePushTransition.swift in Sources */, D688F599258ACAAE003A0A73 /* BrowserWebViewController.swift in Sources */, D688F633258B09BB003A0A73 /* TrackpadScrollGestureRecognizer.swift in Sources */, D6E152A524BFFDF500FDF9D3 /* AppDelegate.swift in Sources */, @@ -1113,7 +1111,9 @@ D6E152A724BFFDF500FDF9D3 /* SceneDelegate.swift in Sources */, D688F64A258C17F3003A0A73 /* SymbolCache.swift in Sources */, D62BCEE2252553620031D894 /* ActivityView.swift in Sources */, + D688F65A258C2256003A0A73 /* BrowserNavigationController.swift in Sources */, D691A68725223A4700348C4B /* NavigationBar.swift in Sources */, + D688F663258C2479003A0A73 /* UIViewController+Children.swift in Sources */, D691A64E25217C6F00348C4B /* Preferences.swift in Sources */, D6E152A924BFFDF500FDF9D3 /* ContentView.swift in Sources */, );