From ad85c3dc237f3b9f48d1e6afe5b6adbf74e87dc3 Mon Sep 17 00:00:00 2001 From: James Magahern Date: Fri, 31 Jul 2020 14:08:10 -0700 Subject: [PATCH] Back/forward buttons, share button on iPad --- .../Browser View/BrowserViewController.swift | 77 ++++++++++++++---- .../ScriptPolicyControl.swift | 12 +-- .../ScriptPolicyViewController.swift | 9 +-- SBrowser/Tabs/TabPickerViewController.swift | 14 ++-- .../ToolbarViewController.swift | 79 +++++++++++++------ SBrowser/Titlebar and URL Bar/URLBar.swift | 6 +- 6 files changed, 133 insertions(+), 64 deletions(-) diff --git a/SBrowser/Browser View/BrowserViewController.swift b/SBrowser/Browser View/BrowserViewController.swift index 16bb0f5..ffc0e0d 100644 --- a/SBrowser/Browser View/BrowserViewController.swift +++ b/SBrowser/Browser View/BrowserViewController.swift @@ -6,6 +6,7 @@ // import UIKit +import UniformTypeIdentifiers class BrowserViewController: UIViewController, WKNavigationDelegate, UITextFieldDelegate, ScriptPolicyViewControllerDelegate, @@ -24,6 +25,8 @@ class BrowserViewController: UIViewController, WKNavigationDelegate, private var titleObservation: NSKeyValueObservation? private var loadingObservation: NSKeyValueObservation? + private var backButtonObservation: NSKeyValueObservation? + private var forwardButtonObservation: NSKeyValueObservation? override var preferredStatusBarStyle: UIStatusBarStyle { .lightContent } @@ -41,18 +44,49 @@ class BrowserViewController: UIViewController, WKNavigationDelegate, browserView.toolbarView = toolbarController.toolbarView // Refresh button - toolbarController.urlBar.refreshButton.addAction(UIAction(handler: { [weak self] action in - guard let self = self else { return } - if self.webView.isLoading { - self.webView.stopLoading() + toolbarController.urlBar.refreshButton.addAction(UIAction(handler: { [webView] action in + if webView.isLoading { + webView.stopLoading() } else { - self.webView.reload() + webView.reload() + } + }), for: .touchUpInside) + + // Back button + toolbarController.backButton.addAction(UIAction(handler: { [webView] _ in + webView.goBack() + }), for: .touchUpInside) + + // Forward button + toolbarController.forwardButton.addAction(UIAction(handler: { [webView] _ in + webView.goForward() + }), for: .touchUpInside) + + // Share button + toolbarController.shareButton.addAction(UIAction(handler: { [unowned self, webView, toolbarController] _ in + if let url = webView.url { + let itemProvider = NSItemProvider(item: url as NSURL, typeIdentifier: UTType.url.identifier) + let config = UIActivityItemsConfiguration(itemProviders: [ itemProvider ]) + config.metadataProvider = { metadataKey in + switch metadataKey { + case .title: return webView.title + case .messageBody: return webView.title + default: return nil + } + } + + config.previewProvider = { index, intent, suggestedSize in + NSItemProvider(item: self.tab.favicon, typeIdentifier: UTType.image.identifier) + } + + let activityController = UIActivityViewController(activityItemsConfiguration: config) + activityController.popoverPresentationController?.sourceView = toolbarController.shareButton + self.present(activityController, animated: true, completion: nil) } }), for: .touchUpInside) // Script button - toolbarController.scriptControllerIconView.addAction(UIAction(handler: { [weak self] action in - guard let self = self else { return } + toolbarController.scriptControllerIconView.addAction(UIAction(handler: { [unowned self] action in let hostOrigin = self.webView.url?.host ?? "" let loadedScripts = self.tab.allowedScriptOrigins.union(self.tab.blockedScriptOrigins) let scriptViewController = ScriptPolicyViewController(policyManager: self.policyManager, @@ -70,15 +104,13 @@ class BrowserViewController: UIViewController, WKNavigationDelegate, }), for: .touchUpInside) // Dark mode button - toolbarController.darkModeButton.addAction(UIAction(handler: { [weak self] _ in - guard let self = self else { return } - self.tab.bridge.darkModeEnabled = !self.tab.bridge.darkModeEnabled - self.toolbarController.darkModeEnabled = self.tab.bridge.darkModeEnabled + toolbarController.darkModeButton.addAction(UIAction(handler: { [tab, toolbarController] _ in + tab.bridge.darkModeEnabled = !tab.bridge.darkModeEnabled + toolbarController.darkModeEnabled = tab.bridge.darkModeEnabled }), for: .touchUpInside) // Tabs button - toolbarController.windowButton.addAction(UIAction(handler: { [weak self] _ in - guard let self = self else { return } + toolbarController.windowButton.addAction(UIAction(handler: { [unowned self] _ in let tabPickerController = TabPickerViewController(tabController: self.tabController) tabPickerController.delegate = self tabPickerController.selectedTab = self.tab @@ -125,14 +157,25 @@ class BrowserViewController: UIViewController, WKNavigationDelegate, // Load progress updateLoadProgress(forWebView: webView) - loadingObservation = webView.observe(\.estimatedProgress) { [weak self] (webView, observedChange) in - self?.updateLoadProgress(forWebView: webView) + loadingObservation = webView.observe(\.estimatedProgress) { [unowned self] (webView, observedChange) in + self.updateLoadProgress(forWebView: webView) } // Title observer updateTitleAndURL(forWebView: webView) - titleObservation = webView.observe(\.title, changeHandler: { [weak self] (webView, observedChange) in - self?.updateTitleAndURL(forWebView: webView) + titleObservation = webView.observe(\.title, changeHandler: { [unowned self] (webView, observedChange) in + self.updateTitleAndURL(forWebView: webView) + }) + + // Back/forward observer + toolbarController.backButton.isEnabled = webView.canGoBack + backButtonObservation = webView.observe(\.canGoBack, changeHandler: { [toolbarController] (webView, observedChange) in + toolbarController.backButton.isEnabled = webView.canGoBack + }) + + toolbarController.forwardButton.isEnabled = webView.canGoForward + forwardButtonObservation = webView.observe(\.canGoForward, changeHandler: { [toolbarController] (webView, observedChange) in + toolbarController.forwardButton.isEnabled = webView.canGoForward }) // Script blocker button diff --git a/SBrowser/Script Policy UI/ScriptPolicyControl.swift b/SBrowser/Script Policy UI/ScriptPolicyControl.swift index fd09656..44f07c1 100644 --- a/SBrowser/Script Policy UI/ScriptPolicyControl.swift +++ b/SBrowser/Script Policy UI/ScriptPolicyControl.swift @@ -30,16 +30,16 @@ class ScriptPolicyControl: UIControl convenience init() { self.init(frame: .zero) - allowButton.addAction(UIAction(handler: { [weak self] _ in - self?.policyStatus = .allowed - self?.sendActions(for: .valueChanged) + allowButton.addAction(UIAction(handler: { [unowned self] _ in + self.policyStatus = .allowed + self.sendActions(for: .valueChanged) }), for: .touchUpInside) allowButton.imageView?.contentMode = .scaleAspectFit addSubview(allowButton) - denyButton.addAction(UIAction(handler: { [weak self] _ in - self?.policyStatus = .blocked - self?.sendActions(for: .valueChanged) + denyButton.addAction(UIAction(handler: { [unowned self] _ in + self.policyStatus = .blocked + self.sendActions(for: .valueChanged) }), for: .touchUpInside) denyButton.imageView?.contentMode = .scaleAspectFit addSubview(denyButton) diff --git a/SBrowser/Script Policy UI/ScriptPolicyViewController.swift b/SBrowser/Script Policy UI/ScriptPolicyViewController.swift index 4938b3b..c71a152 100644 --- a/SBrowser/Script Policy UI/ScriptPolicyViewController.swift +++ b/SBrowser/Script Policy UI/ScriptPolicyViewController.swift @@ -102,8 +102,7 @@ class ScriptPolicyViewController: UIViewController, UICollectionViewDelegate let otherOriginScripts = loadedScripts.subtracting([ hostOrigin ]) let originItems = [ hostOrigin ] + otherOriginScripts - let switchCellRegistry = UICollectionView.CellRegistration { [weak self] (listCell, indexPath, item) in - guard let self = self else { return } + let switchCellRegistry = UICollectionView.CellRegistration { [unowned self] (listCell, indexPath, item) in var config = listCell.defaultContentConfiguration() if item == Self.enableScriptsForTabItem { config.text = "Allow for Tab" @@ -133,8 +132,7 @@ class ScriptPolicyViewController: UIViewController, UICollectionViewDelegate listCell.contentConfiguration = config } - let scriptPolicyRegistry = UICollectionView.CellRegistration { [weak self] (listCell, indexPath, item) in - guard let self = self else { return } + let scriptPolicyRegistry = UICollectionView.CellRegistration { [unowned self] (listCell, indexPath, item) in var config = listCell.defaultContentConfiguration() config.text = item @@ -196,8 +194,7 @@ class ScriptPolicyViewController: UIViewController, UICollectionViewDelegate self.collectionView = collectionView title = "Script Origin Policy" - navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: UIAction(handler: { [weak self] action in - guard let self = self else { return } + navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: UIAction(handler: { [unowned self] action in if self.didChangeScriptPolicy { self.delegate?.didChangeScriptPolicy() self.delegate?.setScriptsEnabledForTab(self.allowScriptsForTab) diff --git a/SBrowser/Tabs/TabPickerViewController.swift b/SBrowser/Tabs/TabPickerViewController.swift index 682e1e7..2eed318 100644 --- a/SBrowser/Tabs/TabPickerViewController.swift +++ b/SBrowser/Tabs/TabPickerViewController.swift @@ -38,13 +38,12 @@ class TabPickerViewController: UIViewController, UICollectionViewDelegate override func loadView() { var listConfig = UICollectionLayoutListConfiguration(appearance: .grouped) - listConfig.trailingSwipeActionsConfigurationProvider = { [weak self] indexPath in - if self?.dataSource?.snapshot().numberOfItems ?? 0 <= 1 { + listConfig.trailingSwipeActionsConfigurationProvider = { [unowned self] indexPath in + if self.dataSource?.snapshot().numberOfItems ?? 0 <= 1 { return nil } - return UISwipeActionsConfiguration(actions: [ UIContextualAction(style: .destructive, title: "Close", handler: { [weak self] (action, view, completionHandler) in - guard let self = self else { return } + return UISwipeActionsConfiguration(actions: [ UIContextualAction(style: .destructive, title: "Close", handler: { [unowned self] (action, view, completionHandler) in if let item = self.dataSource?.itemIdentifier(for: indexPath), var snapshot = self.dataSource?.snapshot() { if let tab = self.tabController.tab(forIdentifier: item) { self.delegate?.tabPicker(self, willCloseTab: tab) @@ -60,8 +59,7 @@ class TabPickerViewController: UIViewController, UICollectionViewDelegate let listLayout = UICollectionViewCompositionalLayout.list(using: listConfig) let collectionView = UICollectionView(frame: .zero, collectionViewLayout: listLayout) - let registry = UICollectionView.CellRegistration { [weak self] (listCell, indexPath, item) in - guard let self = self else { return } + let registry = UICollectionView.CellRegistration { [unowned self] (listCell, indexPath, item) in var config = listCell.defaultContentConfiguration() if let tab = self.tabController.tab(forIdentifier: item) { @@ -108,9 +106,7 @@ class TabPickerViewController: UIViewController, UICollectionViewDelegate self.collectionView = collectionView self.view = self.collectionView - let newTabButton = UIBarButtonItem(systemItem: .add, primaryAction: UIAction(handler: { [weak self] _ in - guard let self = self else { return } - + let newTabButton = UIBarButtonItem(systemItem: .add, primaryAction: UIAction(handler: { [unowned self] _ in let newTab = self.tabController.createNewTab() self.delegate?.tabPicker(self, didSelectTab: newTab) }), menu: nil) diff --git a/SBrowser/Titlebar and URL Bar/ToolbarViewController.swift b/SBrowser/Titlebar and URL Bar/ToolbarViewController.swift index 9f222fd..71466be 100644 --- a/SBrowser/Titlebar and URL Bar/ToolbarViewController.swift +++ b/SBrowser/Titlebar and URL Bar/ToolbarViewController.swift @@ -12,6 +12,8 @@ class ToolbarButtonView: UIView private var buttonPadding = CGFloat(24.0) private var buttonViews: [UIView] = [] + public var numberOfButtonViews: Int { buttonViews.count } + func addButtonView(_ button: UIView) { buttonViews.append(button) addSubview(button) @@ -54,16 +56,19 @@ class ToolbarView: UIView let containerView = UIView(frame: .zero) let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial)) - let buttonsView = ToolbarButtonView(frame: .zero) let cancelButton = UIButton(type: .system) + let leadingButtonsView = ToolbarButtonView(frame: .zero) + let trailingButtonsView = ToolbarButtonView(frame: .zero) + convenience init() { self.init(frame: .zero) addSubview(backgroundView) addSubview(containerView) - containerView.addSubview(buttonsView) + containerView.addSubview(leadingButtonsView) + containerView.addSubview(trailingButtonsView) cancelButton.setTitle("Cancel", for: .normal) containerView.addSubview(cancelButton) @@ -97,25 +102,44 @@ class ToolbarView: UIView cancelButton.frame = CGRect(origin: CGPoint(x: (containerView.bounds.maxX - cancelButtonSize.width), y: 0), size: CGSize(width: cancelButtonSize.width, height: containerView.bounds.height)) - // Toolbar buttons - let buttonContainerSize = buttonsView.sizeThatFits(containerView.bounds.size) - buttonsView.frame = CGRect(origin: CGPoint(x: (containerView.bounds.maxX - buttonContainerSize.width) + urlBarPadding, y: 0), size: buttonContainerSize) + // Leading toolbar buttons + if leadingButtonsView.numberOfButtonViews > 0 { + let leadingContainerSize = leadingButtonsView.sizeThatFits(containerView.bounds.size) + leadingButtonsView.frame = CGRect(origin: .zero, size: leadingContainerSize) + } else { + leadingButtonsView.frame = .zero + } + + // Trailing toolbar buttons + let trailingContainerSize = trailingButtonsView.sizeThatFits(containerView.bounds.size) + trailingButtonsView.frame = CGRect(origin: CGPoint(x: (containerView.bounds.maxX - trailingContainerSize.width) + urlBarPadding, y: 0), size: trailingContainerSize) var avoidingSize: CGSize = .zero if cancelButtonVisible { cancelButton.alpha = 1.0 - buttonsView.alpha = 0.0 + trailingButtonsView.alpha = 0.0 avoidingSize = cancelButtonSize } else { cancelButton.alpha = 0.0 - buttonsView.alpha = 1.0 + trailingButtonsView.alpha = 1.0 - avoidingSize = buttonContainerSize + avoidingSize = trailingContainerSize } if let urlBar = urlBar { - urlBar.frame = CGRect(origin: .zero, size: CGSize(width: containerView.bounds.width - avoidingSize.width - urlBarPadding, height: containerView.bounds.height)) + let origin = CGPoint( + x: leadingButtonsView.frame.maxX, + y: 0.0 + ) + + urlBar.frame = CGRect( + origin: origin, + size: CGSize( + width: containerView.bounds.width - avoidingSize.width - origin.x, + height: containerView.bounds.height + ) + ) } } } @@ -128,6 +152,8 @@ class ToolbarViewController: UIViewController let shareButton = UIButton(frame: .zero) let darkModeButton = UIButton(frame: .zero) let windowButton = UIButton(frame: .zero) + let backButton = UIButton(frame: .zero) + let forwardButton = UIButton(frame: .zero) var darkModeEnabled: Bool = false { didSet { @@ -156,16 +182,21 @@ class ToolbarViewController: UIViewController // Window button windowButton.setImage(UIImage(systemName: "rectangle.on.rectangle"), for: .normal) + // Back button + backButton.setImage(UIImage(systemName: "chevron.left"), for: .normal) + + // Forward button + forwardButton.setImage(UIImage(systemName: "chevron.right"), for: .normal) + let toolbarAnimationDuration: TimeInterval = 0.3 - urlBar.textField.addAction(.init(handler: { [weak self] _ in - guard let self = self else { return } + urlBar.textField.addAction(.init(handler: { [toolbarView, urlBar] _ in UIView.animate(withDuration: toolbarAnimationDuration) { - self.toolbarView.cancelButtonVisible = self.urlBar.textField.isFirstResponder + toolbarView.cancelButtonVisible = urlBar.textField.isFirstResponder } }), for: [ .editingDidBegin, .editingDidEnd ]) - toolbarView.cancelButton.addAction(.init(handler: { [weak self] action in - self?.urlBar.textField.resignFirstResponder() + toolbarView.cancelButton.addAction(.init(handler: { [urlBar] action in + urlBar.textField.resignFirstResponder() }), for: .touchUpInside) traitCollectionDidChange(nil) @@ -174,18 +205,22 @@ class ToolbarViewController: UIViewController override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) - toolbarView.buttonsView.removeAllButtonViews() + toolbarView.leadingButtonsView.removeAllButtonViews() + toolbarView.trailingButtonsView.removeAllButtonViews() // Setup toolbar based on trait collection if traitCollection.horizontalSizeClass == .compact { - toolbarView.buttonsView.addButtonView(darkModeButton) - toolbarView.buttonsView.addButtonView(scriptControllerIconView) - toolbarView.buttonsView.addButtonView(windowButton) + toolbarView.trailingButtonsView.addButtonView(darkModeButton) + toolbarView.trailingButtonsView.addButtonView(scriptControllerIconView) + toolbarView.trailingButtonsView.addButtonView(windowButton) } else { - toolbarView.buttonsView.addButtonView(darkModeButton) - toolbarView.buttonsView.addButtonView(shareButton) - toolbarView.buttonsView.addButtonView(scriptControllerIconView) - toolbarView.buttonsView.addButtonView(windowButton) + toolbarView.leadingButtonsView.addButtonView(backButton) + toolbarView.leadingButtonsView.addButtonView(forwardButton) + + toolbarView.trailingButtonsView.addButtonView(darkModeButton) + toolbarView.trailingButtonsView.addButtonView(shareButton) + toolbarView.trailingButtonsView.addButtonView(scriptControllerIconView) + toolbarView.trailingButtonsView.addButtonView(windowButton) } } diff --git a/SBrowser/Titlebar and URL Bar/URLBar.swift b/SBrowser/Titlebar and URL Bar/URLBar.swift index 09b42d2..af5db98 100644 --- a/SBrowser/Titlebar and URL Bar/URLBar.swift +++ b/SBrowser/Titlebar and URL Bar/URLBar.swift @@ -54,10 +54,8 @@ class URLBar: UIView textField.clearButtonMode = .whileEditing addSubview(textField) - textField.addAction(.init(handler: { [weak self] _ in - if let self = self { - self.refreshButton.isHidden = self.textField.isFirstResponder - } + textField.addAction(.init(handler: { [textField, refreshButton] _ in + refreshButton.isHidden = textField.isFirstResponder }), for: [ .editingDidBegin, .editingDidEnd ]) refreshButton.tintColor = .secondaryLabel