Tusker/Tusker/Views/Attachments/AttachmentView.swift

223 lines
7.9 KiB
Swift

//
// AttachmentView.swift
// Tusker
//
// Created by Shadowfacts on 8/31/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
import Gifu
import AVFoundation
protocol AttachmentViewDelegate: class {
func attachmentViewGallery(startingAt index: Int) -> UIViewController
func attachmentViewPresent(_ vc: UIViewController, animated: Bool)
}
class AttachmentView: UIImageView, GIFAnimatable {
weak var delegate: AttachmentViewDelegate?
var playImageView: UIImageView?
var attachment: Attachment!
var index: Int!
private var attachmentRequest: ImageCache.Request?
var gifData: Data?
public lazy var animator: Animator? = Animator(withDelegate: self)
init(attachment: Attachment, index: Int) {
super.init(image: nil)
commonInit()
self.attachment = attachment
self.index = index
loadAttachment()
}
deinit {
attachmentRequest?.cancel()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
contentMode = .scaleAspectFill
layer.masksToBounds = true
isUserInteractionEnabled = true
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(imagePressed)))
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
addInteraction(UIContextMenuInteraction(delegate: self))
}
@objc func preferencesChanged() {
if let gifData = gifData {
if Preferences.shared.automaticallyPlayGifs && !isAnimatingGIF {
animate(withGIFData: gifData)
} else if !Preferences.shared.automaticallyPlayGifs && isAnimatingGIF {
stopAnimatingGIF()
}
}
}
func loadAttachment() {
guard AttachmentsContainerView.supportedAttachmentTypes.contains(attachment.kind) else {
preconditionFailure("invalid attachment type")
}
switch attachment.kind {
case .image:
loadImage()
case .video:
loadVideo()
case .audio:
loadAudio()
case .gifv:
loadGifv()
default:
preconditionFailure("invalid attachment type")
}
}
func loadImage() {
attachmentRequest = ImageCache.attachments.get(attachment.url) { [weak self] (data) in
guard let self = self, let data = data else { return }
self.attachmentRequest = nil
DispatchQueue.main.async {
if self.attachment.url.pathExtension == "gif" {
self.gifData = data
if Preferences.shared.automaticallyPlayGifs {
self.animate(withGIFData: data)
} else {
self.image = UIImage(data: data)
}
} else {
self.image = UIImage(data: data)
}
}
}
}
func loadVideo() {
let attachmentURL = self.attachment.url
DispatchQueue.global(qos: .userInitiated).async {
let asset = AVURLAsset(url: attachmentURL)
let generator = AVAssetImageGenerator(asset: asset)
generator.appliesPreferredTrackTransform = true
guard let image = try? generator.copyCGImage(at: .zero, actualTime: nil) else { return }
DispatchQueue.main.async { [weak self] in
guard let self = self, self.attachment.url == attachmentURL else { return }
self.image = UIImage(cgImage: image)
}
}
let playImageView = UIImageView(image: UIImage(systemName: "play.circle.fill"))
playImageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(playImageView)
NSLayoutConstraint.activate([
playImageView.widthAnchor.constraint(equalToConstant: 50),
playImageView.heightAnchor.constraint(equalToConstant: 50),
playImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
playImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
])
}
func loadAudio() {
let label = UILabel()
label.text = "Audio Only"
let playImageView = UIImageView(image: UIImage(systemName: "play.circle.fill"))
let stack = UIStackView(arrangedSubviews: [
label,
playImageView
])
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .vertical
stack.spacing = 8
stack.alignment = .center
addSubview(stack)
NSLayoutConstraint.activate([
stack.centerXAnchor.constraint(equalTo: centerXAnchor),
stack.centerYAnchor.constraint(equalTo: centerYAnchor),
playImageView.widthAnchor.constraint(equalToConstant: 50),
playImageView.heightAnchor.constraint(equalToConstant: 50),
])
}
func loadGifv() {
let attachmentURL = self.attachment.url
let asset = AVURLAsset(url: attachmentURL)
DispatchQueue.global(qos: .userInitiated).async {
let generator = AVAssetImageGenerator(asset: asset)
generator.appliesPreferredTrackTransform = true
guard let image = try? generator.copyCGImage(at: .zero, actualTime: nil) else { return }
DispatchQueue.main.async { [weak self] in
guard let self = self, self.attachment.url == attachmentURL else { return }
self.image = UIImage(cgImage: image)
}
}
let gifvView = GifvAttachmentView(asset: asset, gravity: .resizeAspectFill)
gifvView.translatesAutoresizingMaskIntoConstraints = false
addSubview(gifvView)
NSLayoutConstraint.activate([
gifvView.leadingAnchor.constraint(equalTo: leadingAnchor),
gifvView.trailingAnchor.constraint(equalTo: trailingAnchor),
gifvView.topAnchor.constraint(equalTo: topAnchor),
gifvView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
override func display(_ layer: CALayer) {
super.display(layer)
updateImageIfNeeded()
}
func showGallery() {
if let delegate = delegate {
let gallery = delegate.attachmentViewGallery(startingAt: index)
delegate.attachmentViewPresent(gallery, animated: true)
}
}
@objc func imagePressed() {
showGallery()
}
}
extension AttachmentView: UIContextMenuInteractionDelegate {
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil, previewProvider: { () -> UIViewController? in
if self.attachment.kind == .image {
return AttachmentPreviewViewController(attachment: self.attachment)
} else if self.attachment.kind == .gifv {
let vc = GifvAttachmentViewController(attachment: self.attachment)
vc.preferredContentSize = self.image?.size ?? .zero
return vc
} else {
return self.delegate?.attachmentViewGallery(startingAt: self.index)
}
}, actionProvider: nil)
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
animator.addCompletion {
animator.preferredCommitStyle = .pop
if let gallery = animator.previewViewController as? GalleryViewController {
self.delegate?.attachmentViewPresent(gallery, animated: true)
} else {
self.showGallery()
}
}
}
}