Add support for iOS 13 previewing and actions

This commit is contained in:
Shadowfacts 2019-06-04 17:04:37 -04:00
parent 2c452b08e8
commit a89fb56a60
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
12 changed files with 168 additions and 56 deletions

View File

@ -58,8 +58,6 @@ class ConversationTableViewController: EnhancedTableViewController {
self.tableView.scrollToRow(at: indexPath, at: .middle, animated: false) self.tableView.scrollToRow(at: indexPath, at: .middle, animated: false)
} }
} }
registerForPreviewing(with: self, sourceView: view)
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {

View File

@ -56,8 +56,6 @@ class NotificationsTableViewController: EnhancedTableViewController {
self.older = pagination?.older self.older = pagination?.older
} }
registerForPreviewing(with: self, sourceView: view)
userActivity = UserActivityManager.checkNotificationsActivity() userActivity = UserActivityManager.checkNotificationsActivity()
} }

View File

@ -89,8 +89,6 @@ class ProfileTableViewController: EnhancedTableViewController, PreferencesAdapti
add(loadingVC!) add(loadingVC!)
shouldLoadOnAccountIDSet = true shouldLoadOnAccountIDSet = true
} }
registerForPreviewing(with: self, sourceView: view)
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {

View File

@ -82,8 +82,6 @@ class TimelineTableViewController: EnhancedTableViewController {
self.newer = pagination?.newer self.newer = pagination?.newer
self.older = pagination?.older self.older = pagination?.older
} }
registerForPreviewing(with: self, sourceView: view)
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
@ -119,6 +117,8 @@ class TimelineTableViewController: EnhancedTableViewController {
return cell return cell
} }
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == statusIDs.count - 1 { if indexPath.row == statusIDs.count - 1 {
guard let older = older else { return } guard let older = older else { return }

View File

@ -7,6 +7,7 @@
// //
import UIKit import UIKit
import SafariServices
class EnhancedTableViewController: UITableViewController { class EnhancedTableViewController: UITableViewController {
@ -36,3 +37,35 @@ class EnhancedTableViewController: UITableViewController {
} }
} }
extension EnhancedTableViewController {
override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
if let cell = tableView.cellForRow(at: indexPath) as? UITableViewCell & MenuPreviewProvider {
let cellLocation = cell.convert(point, from: tableView)
guard let (previewProvider, actionsProvider) = cell.getPreviewProviders(for: cellLocation, sourceViewController: self) else {
return nil
}
let actionProvider: UIContextMenuActionProvider = { (elements) in
return UIMenu<UIAction>.create(title: "test", children: elements + actionsProvider())
}
return UIContextMenuConfiguration(identifier: nil, previewProvider: previewProvider, actionProvider: actionProvider)
} else {
return nil
}
}
override func tableView(_ tableView: UITableView, willCommitMenuWithAnimator animator: UIContextMenuInteractionCommitAnimating) {
if /*animator.preferredCommitStyle == .pop,*/ // preferredCommitStyle is always .dismiss, see FB6113554
let viewController = animator.previewViewController {
animator.addCompletion {
if viewController is LargeImageViewController || viewController is SFSafariViewController {
self.present(viewController, animated: true)
} else {
self.show(viewController, sender: nil)
}
}
}
}
}

View File

@ -16,7 +16,7 @@ class LoadingViewController: UIViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
activityIndicator = UIActivityIndicatorView(style: .whiteLarge) activityIndicator = UIActivityIndicatorView(style: .large)
activityIndicator.color = .darkGray activityIndicator.color = .darkGray
activityIndicator.translatesAutoresizingMaskIntoConstraints = false activityIndicator.translatesAutoresizingMaskIntoConstraints = false

View File

@ -8,31 +8,65 @@
import UIKit import UIKit
import SafariServices import SafariServices
import Pachyderm
protocol PreviewViewControllerProvider { protocol MenuPreviewProvider {
func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? typealias PreviewProviders = (content: UIContextMenuContentPreviewProvider, actions: () -> [UIAction])
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders?
} }
@objc extension UITableViewController: UIViewControllerPreviewingDelegate { extension MenuPreviewProvider {
public func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
if let indexPath = tableView.indexPathForRow(at: location), fileprivate func present(_ vc: UIViewController) {
let cell = tableView.cellForRow(at: indexPath) as? UITableViewCell & PreviewViewControllerProvider { UIApplication.shared.keyWindow!.rootViewController!.present(vc, animated: true)
let cellLocation = cell.convert(location, from: tableView) }
if let vc = cell.getPreviewViewController(forLocation: cellLocation, sourceViewController: self) {
// previewingContext.sourceRect = tableView.rectForRow(at: indexPath) func actionsForProfile(accountID: String) -> [UIAction] {
return vc guard let account = MastodonCache.account(for: accountID) else { return [] }
return [
UIAction(__title: "Open in Safari", image: UIImage(systemName: "safari")) { (_) in
self.present(SFSafariViewController(url: account.url))
},
UIAction(__title: "Send Message", image: UIImage(systemName: "envelope")) { (_) in
self.present(UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct)))
},
UIAction(__title: "Share...", image: UIImage(systemName: "square.and.arrow.up")) { (_) in
self.present(UIActivityViewController(activityItems: [account.url], applicationActivities: nil))
} }
} ]
return nil
} }
public func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) { func actionsForURL(_ url: URL) -> [UIAction] {
if viewControllerToCommit is LargeImageViewController || viewControllerToCommit is SFSafariViewController { return [
present(viewControllerToCommit, animated: false) UIAction(__title: "Open in Safari", image: UIImage(systemName: "safari")) { (_) in
} else { self.present(SFSafariViewController(url: url))
navigationController!.pushViewController(viewControllerToCommit, animated: false) },
} UIAction(__title: "Share...", image: UIImage(systemName: "square.and.arrow.up")) { (_) in
self.present(UIActivityViewController(activityItems: [url], applicationActivities: nil))
}
]
} }
func actionsForHashtag(_ hashtag: Hashtag) -> [UIAction] {
return actionsForURL(hashtag.url)
}
func actionsForStatus(statusID: String) -> [UIAction] {
guard let status = MastodonCache.status(for: statusID) else { return [] }
return [
UIAction(__title: "Reply", image: UIImage(systemName: "arrowshape.turn.up.left")) { (_) in
self.present(UINavigationController(rootViewController: ComposeViewController(inReplyTo: statusID)))
},
UIAction(__title: "Open in Safari", image: UIImage(systemName: "safari")) { (_) in
self.present(SFSafariViewController(url: status.url!))
},
UIAction(__title: "Share...", image: UIImage(systemName: "square.and.arrow.up")) { (_) in
self.present(UIActivityViewController(activityItems: [status.url!], applicationActivities: nil))
}
]
}
} }

View File

@ -179,14 +179,27 @@ class ActionNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
} }
extension ActionNotificationTableViewCell: PreviewViewControllerProvider { extension ActionNotificationTableViewCell: MenuPreviewProvider {
func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? { func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
if avatarContainerView.frame.contains(location) { if avatarContainerView.frame.contains(location) {
return ProfileTableViewController(accountID: notification.account.id) let accountID = notification.account.id
return (content: { ProfileTableViewController(accountID: accountID) }, actions: { self.actionsForProfile(accountID: accountID) })
} else if contentLabel.frame.contains(location), } else if contentLabel.frame.contains(location),
let vc = contentLabel.getViewController(forLinkAt: contentLabel.convert(location, from: self)) { let link = contentLabel.getLink(atPoint: contentLabel.convert(location, from: self)) {
return vc return (
content: { self.contentLabel.getViewController(forLink: link.url, inRange: link.range) },
actions: {
let text = (self.contentLabel.text! as NSString).substring(with: link.range)
if let mention = self.contentLabel.getMention(for: link.url, text: text) {
return self.actionsForProfile(accountID: mention.id)
} else if let hashtag = self.contentLabel.getHashtag(for: link.url, text: text) {
return self.actionsForHashtag(hashtag)
} else {
return self.actionsForURL(link.url)
}
}
)
} }
return ConversationTableViewController(for: statusID) return (content: { ConversationTableViewController(for: self.statusID) }, actions: { self.actionsForStatus(statusID: self.statusID) })
} }
} }

View File

@ -98,8 +98,8 @@ class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
} }
extension FollowNotificationTableViewCell: PreviewViewControllerProvider { extension FollowNotificationTableViewCell: MenuPreviewProvider {
func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? { func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
return ProfileTableViewController(accountID: accountID) return (content: { ProfileTableViewController(accountID: self.accountID) }, actions: { self.actionsForProfile(accountID: self.accountID) })
} }
} }

View File

@ -141,14 +141,26 @@ class ProfileHeaderTableViewCell: UITableViewCell, PreferencesAdaptive {
} }
extension ProfileHeaderTableViewCell: PreviewViewControllerProvider { extension ProfileHeaderTableViewCell: MenuPreviewProvider {
func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? { func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
let noteLabelPoint = noteLabel.convert(location, from: self) let noteLabelPoint = noteLabel.convert(location, from: self)
if noteLabel.bounds.contains(noteLabelPoint), if noteLabel.bounds.contains(noteLabelPoint),
let vc = noteLabel.getViewController(forLinkAt: noteLabelPoint) { let link = noteLabel.getLink(atPoint: noteLabelPoint) {
return vc return (
content: { self.noteLabel.getViewController(forLink: link.url, inRange: link.range) },
actions: {
let text = (self.noteLabel.text! as NSString).substring(with: link.range)
if let mention = self.noteLabel.getMention(for: link.url, text: text) {
return self.actionsForProfile(accountID: mention.id)
} else if let hashtag = self.noteLabel.getHashtag(for: link.url, text: text) {
return self.actionsForHashtag(hashtag)
} else {
return self.actionsForURL(link.url)
}
}
)
} else {
return nil
} }
// TODO: should this also have peek/pop for avatar/header images?
return nil
} }
} }

View File

@ -234,20 +234,32 @@ extension ConversationMainStatusTableViewCell: AttachmentViewDelegate {
} }
} }
extension ConversationMainStatusTableViewCell: PreviewViewControllerProvider { extension ConversationMainStatusTableViewCell: MenuPreviewProvider {
func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? { func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
if avatarImageView.frame.contains(location) { if avatarImageView.frame.contains(location) {
return ProfileTableViewController(accountID: accountID) return (content: { ProfileTableViewController(accountID: self.accountID) }, actions: { self.actionsForProfile(accountID: self.accountID) })
} else if attachmentsView.frame.contains(location) { } else if attachmentsView.frame.contains(location) {
let attachmentsViewLocation = attachmentsView.convert(location, from: self) let attachmentsViewLocation = attachmentsView.convert(location, from: self)
if let attachmentView = attachmentsView.subviews.first(where: { $0.frame.contains(attachmentsViewLocation) }) as? AttachmentView { if let attachmentView = attachmentsView.subviews.first(where: { $0.frame.contains(attachmentsViewLocation) }) as? AttachmentView {
let image = attachmentView.image! let image = attachmentView.image!
let description = attachmentView.description let description = attachmentView.attachment.description
return delegate?.largeImage(image, description: description, sourceView: attachmentView) return (content: { self.delegate?.largeImage(image, description: description, sourceView: attachmentView) }, actions: { [] })
} }
} else if contentLabel.frame.contains(location), } else if contentLabel.frame.contains(location),
let vc = contentLabel.getViewController(forLinkAt: contentLabel.convert(location, from: self)) { let link = contentLabel.getLink(atPoint: contentLabel.convert(location, from: self)) {
return vc return (
content: { self.contentLabel.getViewController(forLink: link.url, inRange: link.range) },
actions: {
let text = (self.contentLabel.text! as NSString).substring(with: link.range)
if let mention = self.contentLabel.getMention(for: link.url, text: text) {
return self.actionsForProfile(accountID: mention.id)
} else if let hashtag = self.contentLabel.getHashtag(for: link.url, text: text) {
return self.actionsForHashtag(hashtag)
} else {
return self.actionsForURL(link.url)
}
}
)
} }
return nil return nil
} }

View File

@ -361,21 +361,35 @@ extension StatusTableViewCell: AttachmentViewDelegate {
} }
} }
extension StatusTableViewCell: PreviewViewControllerProvider { extension StatusTableViewCell: MenuPreviewProvider {
func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? {
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
if avatarImageView.frame.contains(location) { if avatarImageView.frame.contains(location) {
return ProfileTableViewController(accountID: accountID) return (content: { ProfileTableViewController(accountID: self.accountID) }, actions: { self.actionsForProfile(accountID: self.accountID) })
} else if attachmentsView.frame.contains(location) { } else if attachmentsView.frame.contains(location) {
let attachmentsViewLocation = attachmentsView.convert(location, from: self) let attachmentsViewLocation = attachmentsView.convert(location, from: self)
if let attachmentView = attachmentsView.subviews.first(where: { $0.frame.contains(attachmentsViewLocation) }) as? AttachmentView { if let attachmentView = attachmentsView.subviews.first(where: { $0.frame.contains(attachmentsViewLocation) }) as? AttachmentView {
let image = attachmentView.image! let image = attachmentView.image!
let description = attachmentView.attachment.description let description = attachmentView.attachment.description
return delegate?.largeImage(image, description: description, sourceView: attachmentView) return (content: { self.delegate?.largeImage(image, description: description, sourceView: attachmentView) }, actions: { [] })
} }
} else if contentLabel.frame.contains(location), } else if contentLabel.frame.contains(location),
let vc = contentLabel.getViewController(forLinkAt: contentLabel.convert(location, from: self)) { let link = contentLabel.getLink(atPoint: contentLabel.convert(location, from: self)) {
return vc return (
content: { self.contentLabel.getViewController(forLink: link.url, inRange: link.range) },
actions: {
let text = (self.contentLabel.text! as NSString).substring(with: link.range)
if let mention = self.contentLabel.getMention(for: link.url, text: text) {
return self.actionsForProfile(accountID: mention.id)
} else if let hashtag = self.contentLabel.getHashtag(for: link.url, text: text) {
return self.actionsForHashtag(hashtag)
} else {
return self.actionsForURL(link.url)
}
}
)
} }
return ConversationTableViewController(for: statusID) return (content: { ConversationTableViewController(for: self.statusID) }, actions: { self.actionsForStatus(statusID: self.statusID) })
} }
} }