From 4296c243ff561530d80beb9f03d5dc4c65891848 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 23 Nov 2019 22:15:29 -0600 Subject: [PATCH] Implement custom previews for context menus to crop cell separators. Issue #1221 --- NetNewsWire.xcodeproj/project.pbxproj | 10 ++++-- iOS/MasterFeed/MasterFeedViewController.swift | 35 +++++++++++++++---- .../MasterTimelineViewController.swift | 11 +++++- .../CroppingTargetedPreview.swift | 20 +++++++++++ submodules/RSTree | 2 +- 5 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 iOS/UIKit Extensions/CroppingTargetedPreview.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index e19fa382a..f2f16e50a 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -93,6 +93,7 @@ 51627A6723861DA3007B3B4B /* MasterFeedViewController+Drag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51627A6623861DA3007B3B4B /* MasterFeedViewController+Drag.swift */; }; 51627A6923861DED007B3B4B /* MasterFeedViewController+Drop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51627A6823861DED007B3B4B /* MasterFeedViewController+Drop.swift */; }; 51627A6B238629D8007B3B4B /* MasterFeedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51627A6A238629D8007B3B4B /* MasterFeedDataSource.swift */; }; + 51627A93238A3836007B3B4B /* CroppingTargetedPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51627A92238A3836007B3B4B /* CroppingTargetedPreview.swift */; }; 516A093723609A3600EAE89B /* SettingsAccountTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 516A091D23609A3600EAE89B /* SettingsAccountTableViewCell.xib */; }; 516A09392360A2AE00EAE89B /* SettingsAccountTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516A09382360A2AE00EAE89B /* SettingsAccountTableViewCell.swift */; }; 516A093B2360A4A000EAE89B /* SettingsTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 516A093A2360A4A000EAE89B /* SettingsTableViewCell.xib */; }; @@ -1271,6 +1272,7 @@ 51627A6623861DA3007B3B4B /* MasterFeedViewController+Drag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MasterFeedViewController+Drag.swift"; sourceTree = ""; }; 51627A6823861DED007B3B4B /* MasterFeedViewController+Drop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MasterFeedViewController+Drop.swift"; sourceTree = ""; }; 51627A6A238629D8007B3B4B /* MasterFeedDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterFeedDataSource.swift; sourceTree = ""; }; + 51627A92238A3836007B3B4B /* CroppingTargetedPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CroppingTargetedPreview.swift; sourceTree = ""; }; 516A091D23609A3600EAE89B /* SettingsAccountTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingsAccountTableViewCell.xib; sourceTree = ""; }; 516A09382360A2AE00EAE89B /* SettingsAccountTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccountTableViewCell.swift; sourceTree = ""; }; 516A093A2360A4A000EAE89B /* SettingsTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingsTableViewCell.xib; sourceTree = ""; }; @@ -1854,14 +1856,17 @@ children = ( 51F85BFA2275D85000C787DC /* Array-Extensions.swift */, 51F85BF42273625800C787DC /* Bundle-Extensions.swift */, + 51627A92238A3836007B3B4B /* CroppingTargetedPreview.swift */, 512AF9C1236ED52C0066F8BE /* ImageHeaderView.swift */, 512AF9DC236F05230066F8BE /* InteractiveLabel.swift */, + 51934CC1230F5963006127BE /* InteractiveNavigationController.swift */, + 51A9A5F42380F6A60033AADF /* ModalNavigationController.swift */, 51EAED95231363EF00A9EEE3 /* NonIntrinsicButton.swift */, 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */, 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */, + 51A9A6092382FD240033AADF /* PoppableGestureRecognizerDelegate.swift */, 512363372369155100951F16 /* RoundedProgressView.swift */, 51C45250226506F400C03939 /* String-Extensions.swift */, - 51934CC1230F5963006127BE /* InteractiveNavigationController.swift */, 5108F6D723763094001ABC45 /* TickMarkSlider.swift */, 51F85BF82274AA7B00C787DC /* UIBarButtonItem-Extensions.swift */, 51F85BF622749FA100C787DC /* UIFont-Extensions.swift */, @@ -1869,8 +1874,6 @@ 51FFF0C3235EE8E5002762AA /* VibrantButton.swift */, 5186A634235EF3A800C97195 /* VibrantLabel.swift */, 5F323808231DF9F000706F6B /* VibrantTableViewCell.swift */, - 51A9A5F42380F6A60033AADF /* ModalNavigationController.swift */, - 51A9A6092382FD240033AADF /* PoppableGestureRecognizerDelegate.swift */, ); path = "UIKit Extensions"; sourceTree = ""; @@ -3962,6 +3965,7 @@ 51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */, 514B7D1F23219F3C00BAC947 /* AddControllerType.swift in Sources */, 5141E7562374A2890013FF27 /* ArticleIconSchemeHandler.swift in Sources */, + 51627A93238A3836007B3B4B /* CroppingTargetedPreview.swift in Sources */, 512AF9DD236F05230066F8BE /* InteractiveLabel.swift in Sources */, 51E3EB3D229AB08300645299 /* ErrorHandler.swift in Sources */, 5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */, diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 1dbc79ffb..113c746e3 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -294,11 +294,22 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { return nil } if node.representedObject is WebFeed { - return makeFeedContextMenu(indexPath: indexPath, includeDeleteRename: true) + return makeFeedContextMenu(node: node, indexPath: indexPath, includeDeleteRename: true) } else { - return makeFolderContextMenu(indexPath: indexPath) + return makeFolderContextMenu(node: node, indexPath: indexPath) } } + + override func tableView(_ tableView: UITableView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { + guard let nodeUniqueId = configuration.identifier as? Int, + let node = coordinator.rootNode.descendantNode(where: { $0.uniqueID == nodeUniqueId }), + let indexPath = dataSource.indexPath(for: node), + let cell = tableView.cellForRow(at: indexPath) else { + return nil + } + + return UITargetedPreview(view: cell, parameters: CroppingPreviewParameters(view: cell)) + } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { becomeFirstResponder() @@ -572,12 +583,22 @@ extension MasterFeedViewController: UIContextMenuInteractionDelegate { return nil } - return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { suggestedActions in + return UIContextMenuConfiguration(identifier: sectionIndex as NSCopying, previewProvider: nil) { suggestedActions in let accountInfoAction = self.getAccountInfoAction(account: account) let deactivateAction = self.deactivateAccountAction(account: account) return UIMenu(title: "", children: [accountInfoAction, deactivateAction]) } } + + func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { + + guard let sectionIndex = configuration.identifier as? Int, + let cell = tableView.headerView(forSection: sectionIndex) else { + return nil + } + + return UITargetedPreview(view: cell, parameters: CroppingPreviewParameters(view: cell)) + } } // MARK: MasterTableViewCellDelegate @@ -785,8 +806,8 @@ private extension MasterFeedViewController { } } - func makeFeedContextMenu(indexPath: IndexPath, includeDeleteRename: Bool) -> UIContextMenuConfiguration { - return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { [ weak self] suggestedActions in + func makeFeedContextMenu(node: Node, indexPath: IndexPath, includeDeleteRename: Bool) -> UIContextMenuConfiguration { + return UIContextMenuConfiguration(identifier: node.uniqueID as NSCopying, previewProvider: nil, actionProvider: { [ weak self] suggestedActions in guard let self = self else { return nil } @@ -819,8 +840,8 @@ private extension MasterFeedViewController { } - func makeFolderContextMenu(indexPath: IndexPath) -> UIContextMenuConfiguration { - return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { [weak self] suggestedActions in + func makeFolderContextMenu(node: Node, indexPath: IndexPath) -> UIContextMenuConfiguration { + return UIContextMenuConfiguration(identifier: node.uniqueID as NSCopying, previewProvider: nil, actionProvider: { [weak self] suggestedActions in guard let self = self else { return nil } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 27a9020e1..ac428fe8d 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -286,7 +286,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner guard let article = dataSource.itemIdentifier(for: indexPath) else { return nil } - return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { [weak self] suggestedActions in + return UIContextMenuConfiguration(identifier: indexPath.row as NSCopying, previewProvider: nil, actionProvider: { [weak self] suggestedActions in guard let self = self else { return nil } @@ -317,6 +317,15 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner } + override func tableView(_ tableView: UITableView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { + guard let row = configuration.identifier as? Int, + let cell = tableView.cellForRow(at: IndexPath(row: row, section: 0)) else { + return nil + } + + return UITargetedPreview(view: cell, parameters: CroppingPreviewParameters(view: cell)) + } + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { becomeFirstResponder() let article = dataSource.itemIdentifier(for: indexPath) diff --git a/iOS/UIKit Extensions/CroppingTargetedPreview.swift b/iOS/UIKit Extensions/CroppingTargetedPreview.swift new file mode 100644 index 000000000..58a19dfbf --- /dev/null +++ b/iOS/UIKit Extensions/CroppingTargetedPreview.swift @@ -0,0 +1,20 @@ +// +// CroppingPreviewParameters.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 11/23/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import UIKit + +class CroppingPreviewParameters: UIPreviewParameters { + + init(view: UIView) { + super.init() + let newBounds = CGRect(x: 1, y: 1, width: view.bounds.width - 2, height: view.bounds.height - 2) + let visiblePath = UIBezierPath(roundedRect: newBounds, cornerRadius: 10) + self.visiblePath = visiblePath + } + +} diff --git a/submodules/RSTree b/submodules/RSTree index 3dc1c288b..a041d4fc0 160000 --- a/submodules/RSTree +++ b/submodules/RSTree @@ -1 +1 @@ -Subproject commit 3dc1c288bb4e15fedf17fa8fc43c1d5cec36af5e +Subproject commit a041d4fc0e45077d28386e7efe4fca3a175584ad