From 09c6204a7397a828b2d9a736b3320514da5ea106 Mon Sep 17 00:00:00 2001 From: James Magahern Date: Thu, 11 Feb 2021 19:20:06 -0800 Subject: [PATCH] Tab picker: Allow multiple selection/deletion --- App/Browser View/BrowserViewController.swift | 8 +- .../ScriptPolicyViewController.swift | 14 +-- App/Tabs/TabPickerViewController.swift | 88 +++++++++++++++++-- App/Utilities/UITraitCollection+MacLike.swift | 20 +++++ SBrowser.xcodeproj/project.pbxproj | 4 + 5 files changed, 112 insertions(+), 22 deletions(-) create mode 100644 App/Utilities/UITraitCollection+MacLike.swift diff --git a/App/Browser View/BrowserViewController.swift b/App/Browser View/BrowserViewController.swift index f49f6be..e89d2fb 100644 --- a/App/Browser View/BrowserViewController.swift +++ b/App/Browser View/BrowserViewController.swift @@ -251,9 +251,11 @@ class BrowserViewController: UIViewController, WKNavigationDelegate, WKUIDelegat activeTabObservation = tabController.$activeTabIndex .receive(on: RunLoop.main) .sink(receiveValue: { [unowned self] (activeTab: Int) in - let tab = tabController.tabs[activeTab] - if self.tab != tab { - self.tab = tab + if activeTab < tabController.tabs.count { + let tab = tabController.tabs[activeTab] + if self.tab != tab { + self.tab = tab + } } }) diff --git a/App/Script Policy UI/ScriptPolicyViewController.swift b/App/Script Policy UI/ScriptPolicyViewController.swift index bc67ec8..e822734 100644 --- a/App/Script Policy UI/ScriptPolicyViewController.swift +++ b/App/Script Policy UI/ScriptPolicyViewController.swift @@ -7,7 +7,7 @@ import UIKit -protocol ScriptPolicyViewControllerDelegate: class { +protocol ScriptPolicyViewControllerDelegate: AnyObject { func didChangeScriptPolicy() func setScriptsEnabledForTab(_ enabled: Bool) } @@ -96,16 +96,7 @@ class ScriptPolicyViewController: UIViewController, UICollectionViewDelegate } override var traitCollection: UITraitCollection { - get { - let actualTraits = super.traitCollection - if actualTraits.userInterfaceIdiom == .mac { - // Override traits to be iPad like on mac. We dont want small list cells here. - let desiredTraits = UITraitCollection(userInterfaceIdiom: .pad) - return UITraitCollection(traitsFrom: [ actualTraits, desiredTraits ]) - } - - return actualTraits - } + get { return super.traitCollection.alwaysPadLike() } } private static let enableScriptsForTabItem: String = "enableScriptsForTab" @@ -229,6 +220,7 @@ class ScriptPolicyViewController: UIViewController, UICollectionViewDelegate override func loadView() { self.view = collectionView + self.view.backgroundColor = .systemGroupedBackground } // MARK: UICollectionViewDelegate diff --git a/App/Tabs/TabPickerViewController.swift b/App/Tabs/TabPickerViewController.swift index c58b3fa..e9b2ce4 100644 --- a/App/Tabs/TabPickerViewController.swift +++ b/App/Tabs/TabPickerViewController.swift @@ -7,7 +7,7 @@ import UIKit -protocol TabPickerViewControllerDelegate: class +protocol TabPickerViewControllerDelegate: AnyObject { func tabPicker(_ picker: TabPickerViewController, didSelectTab tab: Tab) func tabPicker(_ picker: TabPickerViewController, willCloseTab tab: Tab) @@ -22,9 +22,27 @@ class TabPickerViewController: UIViewController, UICollectionViewDelegate typealias TabID = UUID + private var selectedTabsForEditing: Set = [] private var collectionView: UICollectionView? private var dataSource: UICollectionViewDiffableDataSource? + override var traitCollection: UITraitCollection { + get { return super.traitCollection.alwaysPadLike() } + } + + private lazy var newTabButton: UIBarButtonItem = { + UIBarButtonItem(systemItem: .add, primaryAction: UIAction(handler: { [unowned self] _ in + let newTab = self.tabController.createNewTab(url: nil) + self.delegate?.tabPicker(self, didSelectTab: newTab) + }), menu: nil) + }() + + lazy var deleteTabButton: UIBarButtonItem = { + UIBarButtonItem(systemItem: .trash, primaryAction: UIAction(handler: { [unowned self] _ in + deleteSelectedTabs() + }), menu: nil) + }() + init(tabController: TabController) { self.tabController = tabController super.init(nibName: nil, bundle: nil) @@ -58,6 +76,8 @@ class TabPickerViewController: UIViewController, UICollectionViewDelegate let listLayout = UICollectionViewCompositionalLayout.list(using: listConfig) let collectionView = UICollectionView(frame: .zero, collectionViewLayout: listLayout) + collectionView.allowsMultipleSelectionDuringEditing = true + collectionView.backgroundColor = .systemGroupedBackground let registry = UICollectionView.CellRegistration { [unowned self] (listCell, indexPath, item) in var config = listCell.defaultContentConfiguration() @@ -85,7 +105,7 @@ class TabPickerViewController: UIViewController, UICollectionViewDelegate config.imageProperties.maximumSize = CGSize(width: 21.0, height: 21.0) config.imageProperties.cornerRadius = 3.0 - if tab == self.selectedTab { + if self.selectedTab == tab { listCell.accessories = [ .checkmark() ] } else { listCell.accessories = [] @@ -114,16 +134,68 @@ class TabPickerViewController: UIViewController, UICollectionViewDelegate self.collectionView = collectionView self.view = self.collectionView - let newTabButton = UIBarButtonItem(systemItem: .add, primaryAction: UIAction(handler: { [unowned self] _ in - let newTab = self.tabController.createNewTab(url: nil) - self.delegate?.tabPicker(self, didSelectTab: newTab) - }), menu: nil) + configureNavigationButtons(forEditing: isEditing) + } + + private func configureNavigationButtons(forEditing: Bool) { + if !forEditing { + navigationItem.rightBarButtonItem = newTabButton + } else { + deleteTabButton.isEnabled = collectionView?.indexPathsForSelectedItems?.count ?? 0 > 0 + navigationItem.rightBarButtonItem = deleteTabButton + } - navigationItem.rightBarButtonItem = newTabButton + navigationItem.leftBarButtonItem = editButtonItem + } + + override func setEditing(_ editing: Bool, animated: Bool) { + super.setEditing(editing, animated: animated) + + if let collectionView = collectionView { + collectionView.isEditing = editing + } + + configureNavigationButtons(forEditing: editing) + } + + private func deleteSelectedTabs() { + guard let dataSource = dataSource else { return } + + var snapshot = dataSource.snapshot() + for tab in selectedTabsForEditing { + snapshot.deleteItems([ tab.identifier ]) + self.delegate?.tabPicker(self, willCloseTab: tab) + self.tabController.closeTab(tab) + } + + dataSource.apply(snapshot, animatingDifferences: true) } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let tab = tabController.tabs[indexPath.row] - delegate?.tabPicker(self, didSelectTab: tab) + + if !isEditing { + delegate?.tabPicker(self, didSelectTab: tab) + } else { + deleteTabButton.isEnabled = collectionView.indexPathsForSelectedItems?.count ?? 0 > 0 + selectedTabsForEditing.update(with: tab) + } + } + + func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { + if isEditing { + let tab = tabController.tabs[indexPath.row] + selectedTabsForEditing.remove(tab) + + deleteTabButton.isEnabled = collectionView.indexPathsForSelectedItems?.count ?? 0 > 0 + } + } + + func collectionView(_ collectionView: UICollectionView, shouldBeginMultipleSelectionInteractionAt indexPath: IndexPath) -> Bool { + true + } + + func collectionView(_ collectionView: UICollectionView, didBeginMultipleSelectionInteractionAt indexPath: IndexPath) { + isEditing = true } } diff --git a/App/Utilities/UITraitCollection+MacLike.swift b/App/Utilities/UITraitCollection+MacLike.swift new file mode 100644 index 0000000..90c7ad4 --- /dev/null +++ b/App/Utilities/UITraitCollection+MacLike.swift @@ -0,0 +1,20 @@ +// +// UITraitCollection+MacLike.swift +// App +// +// Created by James Magahern on 2/11/21. +// + +import UIKit + +extension UITraitCollection { + public func alwaysPadLike() -> UITraitCollection { + if self.userInterfaceIdiom == .mac { + // Override traits to be iPad like on mac. We dont want small list cells here. + let desiredTraits = UITraitCollection(userInterfaceIdiom: .pad) + return UITraitCollection(traitsFrom: [ self, desiredTraits ]) + } + + return self + } +} diff --git a/SBrowser.xcodeproj/project.pbxproj b/SBrowser.xcodeproj/project.pbxproj index f7be5c6..1db60d7 100644 --- a/SBrowser.xcodeproj/project.pbxproj +++ b/SBrowser.xcodeproj/project.pbxproj @@ -52,6 +52,7 @@ CDCE2664251AA80F007FE92A /* DocumentControlViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDCE2663251AA80F007FE92A /* DocumentControlViewController.swift */; }; CDCE2666251AA840007FE92A /* StackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDCE2665251AA840007FE92A /* StackView.swift */; }; CDCE2668251AAA9A007FE92A /* FontSizeAdjustView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDCE2667251AAA9A007FE92A /* FontSizeAdjustView.swift */; }; + CDEDD8AA25D62ADB00862605 /* UITraitCollection+MacLike.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDEDD8A925D62ADB00862605 /* UITraitCollection+MacLike.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -133,6 +134,7 @@ CDCE2663251AA80F007FE92A /* DocumentControlViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentControlViewController.swift; sourceTree = ""; }; CDCE2665251AA840007FE92A /* StackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackView.swift; sourceTree = ""; }; CDCE2667251AAA9A007FE92A /* FontSizeAdjustView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSizeAdjustView.swift; sourceTree = ""; }; + CDEDD8A925D62ADB00862605 /* UITraitCollection+MacLike.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITraitCollection+MacLike.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -315,6 +317,7 @@ CD7A8918251989C90075991E /* UIKeyCommand+ConvInit.swift */, 1ADFF4C624CA6DEB006DC7AE /* UIEdgeInsets+Layout.swift */, 1AB88F0524D4D3A90006F850 /* UIGestureRecognizer+Actions.swift */, + CDEDD8A925D62ADB00862605 /* UITraitCollection+MacLike.swift */, ); path = Utilities; sourceTree = ""; @@ -468,6 +471,7 @@ CDCE2666251AA840007FE92A /* StackView.swift in Sources */, CD853BD124E778B800D2BDCC /* History.xcdatamodeld in Sources */, 1AD31040252545BF00A4A952 /* FindOnPageView.swift in Sources */, + CDEDD8AA25D62ADB00862605 /* UITraitCollection+MacLike.swift in Sources */, CD7A8919251989C90075991E /* UIKeyCommand+ConvInit.swift in Sources */, 1ADFF4C724CA6DEB006DC7AE /* UIEdgeInsets+Layout.swift in Sources */, 1ADFF4AE24C8ED32006DC7AE /* ResourcePolicyManager.swift in Sources */,