From d3c196949eee0c119c86742f4031d3a4b04faa8c Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 19 Dec 2020 22:43:43 -0500 Subject: [PATCH] Re-add navigation bar --- Gemini-iOS/BrowserNavigationController.swift | 64 ++++++++++++---- Gemini-iOS/NavigationBarView.swift | 77 ++++++++++++++++++-- Gemini.xcodeproj/project.pbxproj | 4 + 3 files changed, 125 insertions(+), 20 deletions(-) diff --git a/Gemini-iOS/BrowserNavigationController.swift b/Gemini-iOS/BrowserNavigationController.swift index 913dc0c..b45e08a 100644 --- a/Gemini-iOS/BrowserNavigationController.swift +++ b/Gemini-iOS/BrowserNavigationController.swift @@ -19,17 +19,33 @@ class BrowserNavigationController: UIViewController { private var currentBrowserVC: BrowserWebViewController! private var browserContainer: UIView! + private var navBarView: NavigationBarView! private var toolbarView: ToolbarView! private var gestureState: GestureState? private var trackingScroll = false + private var scrollStartedBelowEnd = false private var prevScrollViewContentOffset: CGPoint? private var toolbarOffset: CGFloat = 0 { didSet { - toolbarView.transform = CGAffineTransform(translationX: 0, y: toolbarOffset) + let realOffset = toolbarOffset * max(toolbarView.bounds.height, navBarView.bounds.height) + toolbarView.transform = CGAffineTransform(translationX: 0, y: realOffset) + navBarView.transform = CGAffineTransform(translationX: 0, y: -realOffset) + + if (oldValue <= 0.5 && toolbarOffset > 0.5) || (oldValue > 0.5 && toolbarOffset <= 0.5) { + setNeedsStatusBarAppearanceUpdate() + } } } + override var prefersStatusBarHidden: Bool { + toolbarOffset > 0.5 + } + + override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { + .slide + } + private var cancellables = [AnyCancellable]() init(navigator: NavigationManager) { @@ -55,6 +71,15 @@ class BrowserNavigationController: UIViewController { currentBrowserVC.scrollViewDelegate = self embedChild(currentBrowserVC, in: browserContainer) + navBarView = NavigationBarView(navigator: navigator) + navBarView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(navBarView) + NSLayoutConstraint.activate([ + navBarView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + navBarView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + navBarView.topAnchor.constraint(equalTo: view.topAnchor), + ]) + toolbarView = ToolbarView(navigator: navigator) toolbarView.showShareSheet = self.showShareSheet toolbarView.showPreferences = self.showPreferences @@ -86,8 +111,14 @@ class BrowserNavigationController: UIViewController { } private func setBrowserVCSafeAreaInsets(_ vc: BrowserWebViewController) { - guard let toolbarView = toolbarView else { return } - vc.additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: toolbarView.bounds.height - view.safeAreaInsets.bottom - toolbarOffset, right: 0) + guard let toolbarView = toolbarView, + let navBarView = navBarView else { return } + vc.additionalSafeAreaInsets = UIEdgeInsets( + top: navBarView.bounds.height - view.safeAreaInsets.top, + left: 0, + bottom: toolbarView.bounds.height - view.safeAreaInsets.bottom, + right: 0 + ) } private func onNavigate(_ operation: NavigationManager.Operation) { @@ -118,6 +149,11 @@ class BrowserNavigationController: UIViewController { currentBrowserVC = newVC currentBrowserVC.scrollViewDelegate = self embedChild(newVC, in: browserContainer) + + UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseInOut) { + self.toolbarOffset = 0 + } + } private let startEdgeNavigationSwipeDistance: CGFloat = 75 @@ -253,6 +289,7 @@ extension BrowserNavigationController: UIScrollViewDelegate { func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { trackingScroll = true prevScrollViewContentOffset = scrollView.contentOffset + scrollStartedBelowEnd = scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.bounds.height + scrollView.safeAreaInsets.bottom) } func scrollViewDidScroll(_ scrollView: UIScrollView) { @@ -262,19 +299,18 @@ extension BrowserNavigationController: UIScrollViewDelegate { let delta = scrollView.contentOffset.y - prevOffset.y - let belowEnd = scrollView.contentOffset.y > scrollView.contentSize.height - scrollView.bounds.height + scrollView.safeAreaInsets.bottom + let belowEnd = scrollView.contentOffset.y > (scrollView.contentSize.height - scrollView.bounds.height + scrollView.safeAreaInsets.bottom) if belowEnd { - UIView.animate(withDuration: 0.15, delay: 0, options: .curveEaseInOut) { - self.toolbarOffset = 0 - } completion: { (_) in - self.setBrowserVCSafeAreaInsets(self.currentBrowserVC) + if scrollStartedBelowEnd { + UIView.animate(withDuration: 0.15, delay: 0, options: .curveEaseInOut) { + self.toolbarOffset = 0 + } + trackingScroll = false } - trackingScroll = false - } else if delta > 0 || (delta < 0 && toolbarOffset < toolbarView.bounds.height) { - toolbarOffset = max(0, min(toolbarView.bounds.height, toolbarOffset + delta)) - - setBrowserVCSafeAreaInsets(currentBrowserVC) + } else if delta > 0 || (delta < 0 && toolbarOffset < 1) { + let normalizedDelta = delta / max(toolbarView.bounds.height, navBarView.bounds.height) + toolbarOffset = max(0, min(1, toolbarOffset + normalizedDelta)) } } @@ -286,7 +322,7 @@ extension BrowserNavigationController: UIScrollViewDelegate { if velocity.y < 0 { finalOffset = 0 } else { - finalOffset = toolbarView.bounds.height + finalOffset = 1 } UIView.animate(withDuration: 0.15, delay: 0, options: .curveEaseOut) { diff --git a/Gemini-iOS/NavigationBarView.swift b/Gemini-iOS/NavigationBarView.swift index 9b6bd8b..8e6c2e5 100644 --- a/Gemini-iOS/NavigationBarView.swift +++ b/Gemini-iOS/NavigationBarView.swift @@ -6,15 +6,80 @@ // import UIKit +import BrowserCore +import Combine class NavigationBarView: UIView { - /* - // Only override draw() if you perform custom drawing. - // An empty implementation adversely affects performance during animation. - override func draw(_ rect: CGRect) { - // Drawing code + let navigator: NavigationManager + + private var border: UIView! + private var textField: UITextField! + + private var cancellables = [AnyCancellable]() + + init(navigator: NavigationManager) { + self.navigator = navigator + + super.init(frame: .zero) + + backgroundColor = .systemBackground + + border = UIView() + border.backgroundColor = UIColor(white: traitCollection.userInterfaceStyle == .dark ? 0.25 : 0.75, alpha: 1) + border.translatesAutoresizingMaskIntoConstraints = false + addSubview(border) + NSLayoutConstraint.activate([ + border.leadingAnchor.constraint(equalTo: leadingAnchor), + border.trailingAnchor.constraint(equalTo: trailingAnchor), + border.bottomAnchor.constraint(equalTo: bottomAnchor), + border.heightAnchor.constraint(equalToConstant: 1), + ]) + + textField = UITextField() + textField.text = navigator.displayURL + textField.borderStyle = .roundedRect + textField.keyboardType = .URL + textField.autocapitalizationType = .none + textField.autocorrectionType = .no + textField.addTarget(self, action: #selector(commitURL), for: .editingDidEnd) + textField.translatesAutoresizingMaskIntoConstraints = false + addSubview(textField) + NSLayoutConstraint.activate([ + textField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8), + textField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8), + textField.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), + textField.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -9), + ]) + + navigator.$currentURL + .sink { (newURL) in + // can't use navigator.displayURL because the publisher fires before the underlying value is updated, so the displayURL getter returns the old value + var components = URLComponents(url: newURL, resolvingAgainstBaseURL: false)! + if components.port == 1965 { + components.port = nil + } + self.textField.text = components.string! + } + .store(in: &cancellables) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + border.backgroundColor = UIColor(white: traitCollection.userInterfaceStyle == .dark ? 0.25 : 0.75, alpha: 1) + } + + @objc private func commitURL() { + if let text = textField.text, let url = URL(string: text) { + navigator.changeURL(url) + } else { + textField.text = navigator.displayURL + } } - */ } diff --git a/Gemini.xcodeproj/project.pbxproj b/Gemini.xcodeproj/project.pbxproj index b379242..f4fa80a 100644 --- a/Gemini.xcodeproj/project.pbxproj +++ b/Gemini.xcodeproj/project.pbxproj @@ -55,6 +55,7 @@ D69F00AC24BE9DD300E37622 /* GeminiDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69F00AB24BE9DD300E37622 /* GeminiDataTask.swift */; }; D69F00AE24BEA29100E37622 /* GeminiResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69F00AD24BEA29100E37622 /* GeminiResponse.swift */; }; D6BC9AB3258E8E13008652BC /* ToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9AB2258E8E13008652BC /* ToolbarView.swift */; }; + D6BC9ABC258E9862008652BC /* NavigationBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9ABB258E9862008652BC /* NavigationBarView.swift */; }; D6DA5783252396030048B65A /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DA5782252396030048B65A /* View+Extensions.swift */; }; D6E1529824BFAAA400FDF9D3 /* BrowserWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E1529624BFAAA400FDF9D3 /* BrowserWindowController.swift */; }; D6E1529924BFAAA400FDF9D3 /* BrowserWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6E1529724BFAAA400FDF9D3 /* BrowserWindowController.xib */; }; @@ -323,6 +324,7 @@ D69F00AD24BEA29100E37622 /* GeminiResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiResponse.swift; sourceTree = ""; }; D69F00AF24BEA84D00E37622 /* NavigationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationManager.swift; sourceTree = ""; }; D6BC9AB2258E8E13008652BC /* ToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarView.swift; sourceTree = ""; }; + D6BC9ABB258E9862008652BC /* NavigationBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarView.swift; sourceTree = ""; }; D6DA5782252396030048B65A /* View+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = ""; }; D6E1529624BFAAA400FDF9D3 /* BrowserWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserWindowController.swift; sourceTree = ""; }; D6E1529724BFAAA400FDF9D3 /* BrowserWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BrowserWindowController.xib; sourceTree = ""; }; @@ -588,6 +590,7 @@ D688F632258B09BB003A0A73 /* TrackpadScrollGestureRecognizer.swift */, D688F662258C2479003A0A73 /* UIViewController+Children.swift */, D6BC9AB2258E8E13008652BC /* ToolbarView.swift */, + D6BC9ABB258E9862008652BC /* NavigationBarView.swift */, D691A6762522382E00348C4B /* BrowserViewController.swift */, D6E152A824BFFDF500FDF9D3 /* ContentView.swift */, D691A68625223A4600348C4B /* NavigationBar.swift */, @@ -1120,6 +1123,7 @@ D688F663258C2479003A0A73 /* UIViewController+Children.swift in Sources */, D691A64E25217C6F00348C4B /* Preferences.swift in Sources */, D6E152A924BFFDF500FDF9D3 /* ContentView.swift in Sources */, + D6BC9ABC258E9862008652BC /* NavigationBarView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };