// // LargeImageContentView.swift // Tusker // // Created by Shadowfacts on 6/17/20. // Copyright © 2020 Shadowfacts. All rights reserved. // import UIKit import Pachyderm import AVFoundation import VisionKit protocol LargeImageContentView: UIView { var animationImage: UIImage? { get } var activityItemsForSharing: [Any] { get } var owner: LargeImageViewController? { get set } func setControlsVisible(_ controlsVisible: Bool) func grayscaleStateChanged() } class LargeImageImageContentView: UIImageView, LargeImageContentView { #if !targetEnvironment(macCatalyst) @available(iOS 16.0, *) private static let analyzer = ImageAnalyzer() private var _analysisInteraction: AnyObject? @available(iOS 16.0, *) private var analysisInteraction: ImageAnalysisInteraction? { _analysisInteraction as? ImageAnalysisInteraction } #endif var animationImage: UIImage? { image! } var activityItemsForSharing: [Any] { [image!] } weak var owner: LargeImageViewController? private var sourceData: Data? init(image: UIImage) { super.init(image: image) contentMode = .scaleAspectFit isUserInteractionEnabled = true #if !targetEnvironment(macCatalyst) if #available(iOS 16.0, *), ImageAnalyzer.isSupported { let interaction = ImageAnalysisInteraction() self._analysisInteraction = interaction interaction.delegate = self interaction.preferredInteractionTypes = .automatic addInteraction(interaction) Task { do { let result = try await LargeImageImageContentView.analyzer.analyze(image, configuration: ImageAnalyzer.Configuration([.text, .machineReadableCode])) interaction.analysis = result } catch { // if analysis fails, we just don't show anything } } } #endif } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setControlsVisible(_ controlsVisible: Bool) { #if !targetEnvironment(macCatalyst) if #available(iOS 16.0, *), let analysisInteraction { // note: passing animated: true here doesn't seem to do anything by itself as of iOS 16.2 (20C5032e) // so the LargeImageViewController handles animating, but we still need to pass true here otherwise it doesn't animate analysisInteraction.setSupplementaryInterfaceHidden(!controlsVisible, animated: true) } #endif } func grayscaleStateChanged() { guard let data = sourceData else { return } let image: UIImage? if Preferences.shared.grayscaleImages { image = ImageGrayscalifier.convert(url: nil, data: data) } else { image = UIImage(data: data) } if let image = image { self.image = image } } } #if !targetEnvironment(macCatalyst) @available(iOS 16.0, *) extension LargeImageImageContentView: ImageAnalysisInteractionDelegate { func presentingViewController(for interaction: ImageAnalysisInteraction) -> UIViewController? { return owner } } #endif class LargeImageGifContentView: GIFImageView, LargeImageContentView { var animationImage: UIImage? { image } var activityItemsForSharing: [Any] { // todo: should gifs share the data? [image].compactMap { $0 } } weak var owner: LargeImageViewController? init(gifController: GIFController) { super.init(image: gifController.lastFrame?.image) contentMode = .scaleAspectFit gifController.attach(to: self) // todo: doing this in the init feels wrong gifController.startAnimating() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setControlsVisible(_ controlsVisible: Bool) { } func grayscaleStateChanged() { // todo } } class LargeImageGifvContentView: GifvAttachmentView, LargeImageContentView { private(set) var animationImage: UIImage? var activityItemsForSharing: [Any] { // todo: what should we share for gifvs? // some SO posts indicate that just sharing a URL to the video should work, but that may need to be a local URL? [] } weak var owner: LargeImageViewController? private let asset: AVURLAsset // The content view needs to supply an intrinsicContentSize for the LargeImageViewController to handle layout/scrolling/zooming correctly override var intrinsicContentSize: CGSize { // This is a really sucky workaround for the fact that in the content view, we don't have access to the size of the underlying video. // There's probably some way of getting this from the AVPlayer/AVAsset directly animationImage?.size ?? CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric) } init(attachment: Attachment, source: UIImageView) { precondition(attachment.kind == .gifv) self.asset = AVURLAsset(url: attachment.url) super.init(asset: asset, gravity: .resizeAspect) self.animationImage = source.image self.player.play() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setControlsVisible(_ controlsVisible: Bool) { } func grayscaleStateChanged() { // no-op, GifvAttachmentView observes the grayscale state itself } }