Add support for iOS 13 previewing and actions
This commit is contained in:
parent
2c452b08e8
commit
a89fb56a60
|
@ -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) {
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
||||||
return vc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
|
func actionsForProfile(accountID: String) -> [UIAction] {
|
||||||
if viewControllerToCommit is LargeImageViewController || viewControllerToCommit is SFSafariViewController {
|
guard let account = MastodonCache.account(for: accountID) else { return [] }
|
||||||
present(viewControllerToCommit, animated: false)
|
return [
|
||||||
} else {
|
UIAction(__title: "Open in Safari", image: UIImage(systemName: "safari")) { (_) in
|
||||||
navigationController!.pushViewController(viewControllerToCommit, animated: false)
|
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))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
// TODO: should this also have peek/pop for avatar/header images?
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) })
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue