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)
}
}
registerForPreviewing(with: self, sourceView: view)
}
override func viewWillAppear(_ animated: Bool) {

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@
//
import UIKit
import SafariServices
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() {
super.viewDidLoad()
activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
activityIndicator = UIActivityIndicatorView(style: .large)
activityIndicator.color = .darkGray
activityIndicator.translatesAutoresizingMaskIntoConstraints = false

View File

@ -8,31 +8,65 @@
import UIKit
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 {
public func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
if let indexPath = tableView.indexPathForRow(at: location),
let cell = tableView.cellForRow(at: indexPath) as? UITableViewCell & PreviewViewControllerProvider {
let cellLocation = cell.convert(location, from: tableView)
if let vc = cell.getPreviewViewController(forLocation: cellLocation, sourceViewController: self) {
// previewingContext.sourceRect = tableView.rectForRow(at: indexPath)
return vc
}
}
return nil
extension MenuPreviewProvider {
fileprivate func present(_ vc: UIViewController) {
UIApplication.shared.keyWindow!.rootViewController!.present(vc, animated: true)
}
public func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
if viewControllerToCommit is LargeImageViewController || viewControllerToCommit is SFSafariViewController {
present(viewControllerToCommit, animated: false)
} else {
navigationController!.pushViewController(viewControllerToCommit, animated: false)
func actionsForProfile(accountID: String) -> [UIAction] {
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))
}
]
}
func actionsForURL(_ url: URL) -> [UIAction] {
return [
UIAction(__title: "Open in Safari", image: UIImage(systemName: "safari")) { (_) in
self.present(SFSafariViewController(url: url))
},
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 {
func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? {
extension ActionNotificationTableViewCell: MenuPreviewProvider {
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
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),
let vc = contentLabel.getViewController(forLinkAt: contentLabel.convert(location, from: self)) {
return vc
}
return ConversationTableViewController(for: statusID)
let link = contentLabel.getLink(atPoint: contentLabel.convert(location, from: self)) {
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 (content: { ConversationTableViewController(for: self.statusID) }, actions: { self.actionsForStatus(statusID: self.statusID) })
}
}

View File

@ -98,8 +98,8 @@ class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
}
extension FollowNotificationTableViewCell: PreviewViewControllerProvider {
func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? {
return ProfileTableViewController(accountID: accountID)
extension FollowNotificationTableViewCell: MenuPreviewProvider {
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
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 {
func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? {
extension ProfileHeaderTableViewCell: MenuPreviewProvider {
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
let noteLabelPoint = noteLabel.convert(location, from: self)
if noteLabel.bounds.contains(noteLabelPoint),
let vc = noteLabel.getViewController(forLinkAt: noteLabelPoint) {
return vc
let link = noteLabel.getLink(atPoint: noteLabelPoint) {
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)
}
// TODO: should this also have peek/pop for avatar/header images?
}
)
} else {
return nil
}
}
}

View File

@ -234,20 +234,32 @@ extension ConversationMainStatusTableViewCell: AttachmentViewDelegate {
}
}
extension ConversationMainStatusTableViewCell: PreviewViewControllerProvider {
func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? {
extension ConversationMainStatusTableViewCell: MenuPreviewProvider {
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
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) {
let attachmentsViewLocation = attachmentsView.convert(location, from: self)
if let attachmentView = attachmentsView.subviews.first(where: { $0.frame.contains(attachmentsViewLocation) }) as? AttachmentView {
let image = attachmentView.image!
let description = attachmentView.description
return delegate?.largeImage(image, description: description, sourceView: attachmentView)
let description = attachmentView.attachment.description
return (content: { self.delegate?.largeImage(image, description: description, sourceView: attachmentView) }, actions: { [] })
}
} else if contentLabel.frame.contains(location),
let vc = contentLabel.getViewController(forLinkAt: contentLabel.convert(location, from: self)) {
return vc
let link = contentLabel.getLink(atPoint: contentLabel.convert(location, from: self)) {
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
}

View File

@ -361,21 +361,35 @@ extension StatusTableViewCell: AttachmentViewDelegate {
}
}
extension StatusTableViewCell: PreviewViewControllerProvider {
func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? {
extension StatusTableViewCell: MenuPreviewProvider {
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
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) {
let attachmentsViewLocation = attachmentsView.convert(location, from: self)
if let attachmentView = attachmentsView.subviews.first(where: { $0.frame.contains(attachmentsViewLocation) }) as? AttachmentView {
let image = attachmentView.image!
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),
let vc = contentLabel.getViewController(forLinkAt: contentLabel.convert(location, from: self)) {
return vc
}
return ConversationTableViewController(for: statusID)
let link = contentLabel.getLink(atPoint: contentLabel.convert(location, from: self)) {
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 (content: { ConversationTableViewController(for: self.statusID) }, actions: { self.actionsForStatus(statusID: self.statusID) })
}
}