From ac28c2ee4f4d24fcab5d0442f2ea0e4eed12e910 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Mon, 14 Nov 2022 20:44:47 -0500 Subject: [PATCH] Add Live Text support to MediaPreviewImageView --- .../Image/MediaPreviewImageView.swift | 45 +++++++++++++++++-- .../MediaPreviewImageViewController.swift | 42 ++++++++++++++++- .../MastodonExtension/ImageAnalyzer.swift | 13 ++++++ 3 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 MastodonSDK/Sources/MastodonExtension/ImageAnalyzer.swift diff --git a/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageView.swift b/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageView.swift index 05f2ce70f..cc31a9639 100644 --- a/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageView.swift +++ b/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageView.swift @@ -9,6 +9,7 @@ import os.log import func AVFoundation.AVMakeRect import UIKit import FLAnimatedImage +import VisionKit final class MediaPreviewImageView: UIScrollView { @@ -28,9 +29,21 @@ final class MediaPreviewImageView: UIScrollView { tapGestureRecognizer.numberOfTapsRequired = 2 return tapGestureRecognizer }() - + private var containerFrame: CGRect? - + + private var _interaction: UIInteraction? = { + if #available(iOS 16.0, *) { + return ImageAnalysisInteraction() + } else { + return nil + } + }() + @available(iOS 16.0, *) + var liveTextInteraction: ImageAnalysisInteraction { + _interaction as! ImageAnalysisInteraction + } + override init(frame: CGRect) { super.init(frame: frame) _init() @@ -55,10 +68,14 @@ extension MediaPreviewImageView { maximumZoomScale = 4.0 addSubview(imageView) - + doubleTapGestureRecognizer.addTarget(self, action: #selector(MediaPreviewImageView.doubleTapGestureRecognizerHandler(_:))) imageView.addGestureRecognizer(doubleTapGestureRecognizer) - + if #available(iOS 16.0, *) { + liveTextInteraction.isSupplementaryInterfaceHidden = true + imageView.addInteraction(liveTextInteraction) + } + delegate = self } @@ -129,6 +146,26 @@ extension MediaPreviewImageView { centerScrollViewContents() contentOffset = CGPoint(x: -contentInset.left, y: -contentInset.top) + + if #available(iOS 16.0, *) { + Task.detached(priority: .userInitiated) { + do { + let analysis = try await ImageAnalyzer.shared.analyze(image, configuration: ImageAnalyzer.Configuration([.text, .machineReadableCode])) + await MainActor.run { + self.liveTextInteraction.analysis = analysis + self.liveTextInteraction.preferredInteractionTypes = .automatic + if self.liveTextInteraction.isSupplementaryInterfaceHidden { + self.liveTextInteraction.setSupplementaryInterfaceHidden(false, animated: true) + } + + } + } catch { + await MainActor.run { + self.liveTextInteraction.preferredInteractionTypes = [] + } + } + } + } os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: setup image for container %s", ((#file as NSString).lastPathComponent), #line, #function, container.frame.debugDescription) } diff --git a/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageViewController.swift b/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageViewController.swift index 127c4c0c0..1c00b3907 100644 --- a/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageViewController.swift +++ b/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageViewController.swift @@ -11,6 +11,7 @@ import Combine import MastodonAsset import MastodonLocalization import FLAnimatedImage +import VisionKit protocol MediaPreviewImageViewControllerDelegate: AnyObject { func mediaPreviewImageViewController(_ viewController: MediaPreviewImageViewController, tapGestureRecognizerDidTrigger tapGestureRecognizer: UITapGestureRecognizer) @@ -31,7 +32,7 @@ final class MediaPreviewImageViewController: UIViewController { let tapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer let longPressGestureRecognizer = UILongPressGestureRecognizer() - + deinit { os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) previewImageView.imageView.af.cancelImageRequest() @@ -42,7 +43,10 @@ extension MediaPreviewImageViewController { override func viewDidLoad() { super.viewDidLoad() - + + if #available(iOS 16.0, *) { + previewImageView.liveTextInteraction.delegate = self + } previewImageView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(previewImageView) NSLayoutConstraint.activate([ @@ -53,7 +57,9 @@ extension MediaPreviewImageViewController { ]) tapGestureRecognizer.addTarget(self, action: #selector(MediaPreviewImageViewController.tapGestureRecognizerHandler(_:))) + tapGestureRecognizer.delegate = self longPressGestureRecognizer.addTarget(self, action: #selector(MediaPreviewImageViewController.longPressGestureRecognizerHandler(_:))) + longPressGestureRecognizer.delegate = self tapGestureRecognizer.require(toFail: previewImageView.doubleTapGestureRecognizer) tapGestureRecognizer.require(toFail: longPressGestureRecognizer) previewImageView.addGestureRecognizer(tapGestureRecognizer) @@ -105,10 +111,42 @@ extension MediaPreviewImageViewController { } +// MARK: - ImageAnalysisInteractionDelegate +@available(iOS 16.0, *) +extension MediaPreviewImageViewController: ImageAnalysisInteractionDelegate { + func contentView(for interaction: ImageAnalysisInteraction) -> UIView? { + view + } + + func presentingViewController(for interaction: ImageAnalysisInteraction) -> UIViewController? { + self + } +} + +// MARK: - UIGestureRecognizerDelegate +extension MediaPreviewImageViewController: UIGestureRecognizerDelegate { + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + if #available(iOS 16.0, *) { + let location = touch.location(in: previewImageView.imageView) + let hasItem = previewImageView.liveTextInteraction.hasInteractiveItem(at: location) + return !hasItem + } else { + return true + } + } +} + // MARK: - UIContextMenuInteractionDelegate extension MediaPreviewImageViewController: UIContextMenuInteractionDelegate { func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + + if #available(iOS 16.0, *) { + if previewImageView.liveTextInteraction.hasInteractiveItem(at: previewImageView.imageView.convert(location, from: previewImageView)) { + return nil + } + } + let previewProvider: UIContextMenuContentPreviewProvider = { () -> UIViewController? in return nil diff --git a/MastodonSDK/Sources/MastodonExtension/ImageAnalyzer.swift b/MastodonSDK/Sources/MastodonExtension/ImageAnalyzer.swift new file mode 100644 index 000000000..cf8a0f437 --- /dev/null +++ b/MastodonSDK/Sources/MastodonExtension/ImageAnalyzer.swift @@ -0,0 +1,13 @@ +// +// ImageAnalyzer.swift +// +// +// Created by Jed Fox on 2022-11-14. +// + +import VisionKit + +@available(iOS 16.0, *) +extension ImageAnalyzer { + public static let shared = ImageAnalyzer() +}