Support gifs in new thumbnail controller and focused attachment view

This commit is contained in:
Shadowfacts 2023-05-03 23:18:52 -04:00
parent ce3b8ba4b3
commit 02daf88db3
4 changed files with 49 additions and 131 deletions

View File

@ -155,7 +155,7 @@ class AttachmentRowController: ViewController {
Label("Delete", systemImage: "trash") Label("Delete", systemImage: "trash")
} }
} previewIfAvailable: { } previewIfAvailable: {
AttachmentThumbnailView(attachment: attachment, fullSize: true) ControllerView(controller: { controller.thumbnailController })
} }
switch controller.descriptionMode { switch controller.descriptionMode {

View File

@ -1,5 +1,5 @@
// //
// AttachmentThumbnailView.swift // AttachmentThumbnailController.swift
// ComposeUI // ComposeUI
// //
// Created by Shadowfacts on 11/10/21. // Created by Shadowfacts on 11/10/21.
@ -14,14 +14,16 @@ class AttachmentThumbnailController: ViewController {
let attachment: DraftAttachment let attachment: DraftAttachment
@Published private var image: UIImage? @Published private var image: UIImage?
@Published private var gifController: GIFController?
@Published private var fullSize: Bool = false @Published private var fullSize: Bool = false
@Published private var imageBackground: Color?
init(attachment: DraftAttachment) { init(attachment: DraftAttachment) {
self.attachment = attachment self.attachment = attachment
} }
func loadImageIfNecessary(fullSize: Bool) { func loadImageIfNecessary(fullSize: Bool) {
guard image == nil || (fullSize && !self.fullSize) else { if (gifController != nil) || (image != nil && fullSize == self.fullSize) {
return return
} }
self.fullSize = fullSize self.fullSize = fullSize
@ -31,29 +33,48 @@ class AttachmentThumbnailController: ViewController {
guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [id], options: nil).firstObject else { guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [id], options: nil).firstObject else {
return return
} }
let size: CGSize let isGIF = PHAssetResource.assetResources(for: asset).contains(where: { $0.uniformTypeIdentifier == UTType.gif.identifier })
if fullSize { if isGIF {
size = PHImageManagerMaximumSize PHImageManager.default().requestImageDataAndOrientation(for: asset, options: nil) { data, typeIdentifier, orientation, info in
guard let data else { return }
if typeIdentifier == UTType.gif.identifier {
self.gifController = GIFController(gifData: data)
} else {
let image = UIImage(data: data)
DispatchQueue.main.async {
self.image = image
}
}
}
} else { } else {
// currently only used as thumbnail in ComposeAttachmentRow let size: CGSize
size = CGSize(width: 80, height: 80) if fullSize {
} size = PHImageManagerMaximumSize
// todo: gifs } else {
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in // currently only used as thumbnail in ComposeAttachmentRow
DispatchQueue.main.async { size = CGSize(width: 80, height: 80)
self.image = image }
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in
DispatchQueue.main.async {
self.image = image
}
} }
} }
case .drawing(_): case .drawing(let drawing):
// todo image = drawing.imageInLightMode(from: drawing.bounds)
break
case .file(let url, let type): case .file(let url, let type):
// todo: videos if type.conforms(to: .movie) {
if let data = try? Data(contentsOf: url) { let asset = AVURLAsset(url: url)
// todo: gifs let imageGenerator = AVAssetImageGenerator(asset: asset)
if type.conforms(to: .image), if let cgImage = try? imageGenerator.copyCGImage(at: .zero, actualTime: nil) {
self.image = UIImage(cgImage: cgImage)
}
} else if let data = try? Data(contentsOf: url) {
if type == .gif {
self.gifController = GIFController(gifData: data)
} else if type.conforms(to: .image),
let image = UIImage(data: data) { let image = UIImage(data: data) {
if fullSize { if fullSize {
image.prepareForDisplay { prepared in image.prepareForDisplay { prepared in
@ -90,7 +111,9 @@ class AttachmentThumbnailController: ViewController {
@ViewBuilder @ViewBuilder
private var content: some SwiftUI.View { private var content: some SwiftUI.View {
if let image = controller.image { if let gifController = controller.gifController {
GIFViewWrapper(controller: gifController)
} else if let image = controller.image {
Image(uiImage: image) Image(uiImage: image)
.resizable() .resizable()
.aspectRatio(config.aspectRatio, contentMode: config.contentMode) .aspectRatio(config.aspectRatio, contentMode: config.contentMode)
@ -124,115 +147,10 @@ extension EnvironmentValues {
} }
} }
struct AttachmentThumbnailView: View {
let attachment: DraftAttachment
let fullSize: Bool
@State private var gifData: Data? = nil
@State private var image: UIImage? = nil
@State private var imageContentMode: ContentMode = .fill
@State private var imageBackgroundColor: Color = .black
@Environment(\.colorScheme) private var colorScheme: ColorScheme
var body: some View {
if let gifData {
GIFViewWrapper(gifData: gifData)
} else if let image {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: imageContentMode)
.background(imageBackgroundColor)
} else {
Image(systemName: placeholderImageName)
.onAppear(perform: self.loadImage)
}
}
private var placeholderImageName: String {
switch colorScheme {
case .light:
return "photo"
case .dark:
return "photo.fill"
@unknown default:
return "photo"
}
}
private func loadImage() {
switch attachment.data {
case .asset(let id):
guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [id], options: nil).firstObject else {
return
}
let size: CGSize
if fullSize {
size = PHImageManagerMaximumSize
} else {
// currently only used as thumbnail in ComposeAttachmentRow
size = CGSize(width: 80, height: 80)
}
let isGIF = PHAssetResource.assetResources(for: asset).contains(where: { $0.uniformTypeIdentifier == UTType.gif.identifier })
if isGIF {
PHImageManager.default().requestImageDataAndOrientation(for: asset, options: nil) { data, typeIdentifier, orientation, info in
if typeIdentifier == UTType.gif.identifier {
self.gifData = data
} else if let data {
let image = UIImage(data: data)
DispatchQueue.main.async {
self.image = image
}
}
}
} else {
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in
DispatchQueue.main.async {
self.image = image
}
}
}
case let .drawing(drawing):
image = drawing.imageInLightMode(from: drawing.bounds)
imageContentMode = .fit
imageBackgroundColor = .white
case .file(let url, let type):
if type.conforms(to: .movie) {
let asset = AVURLAsset(url: url)
let imageGenerator = AVAssetImageGenerator(asset: asset)
if let cgImage = try? imageGenerator.copyCGImage(at: .zero, actualTime: nil) {
self.image = UIImage(cgImage: cgImage)
}
} else if let data = try? Data(contentsOf: url) {
if type == .gif {
self.gifData = data
} else if type.conforms(to: .image),
let image = UIImage(data: data) {
if fullSize {
image.prepareForDisplay {
self.image = $0
}
} else {
image.prepareThumbnail(of: CGSize(width: 80, height: 80)) {
self.image = $0
}
}
}
}
}
}
}
private struct GIFViewWrapper: UIViewRepresentable { private struct GIFViewWrapper: UIViewRepresentable {
typealias UIViewType = GIFImageView typealias UIViewType = GIFImageView
@State private var controller: GIFController @State var controller: GIFController
init(gifData: Data) {
self._controller = State(wrappedValue: GIFController(gifData: gifData))
}
func makeUIView(context: Context) -> GIFImageView { func makeUIView(context: Context) -> GIFImageView {
let view = GIFImageView() let view = GIFImageView()

View File

@ -134,7 +134,7 @@ private struct DraftRow: View {
HStack(spacing: 8) { HStack(spacing: 8) {
ForEach(draft.draftAttachments) { attachment in ForEach(draft.draftAttachments) { attachment in
AttachmentThumbnailView(attachment: attachment, fullSize: false) ControllerView(controller: { AttachmentThumbnailController(attachment: attachment) })
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.clipShape(RoundedRectangle(cornerRadius: 5)) .clipShape(RoundedRectangle(cornerRadius: 5))
.frame(height: 50) .frame(height: 50)

View File

@ -74,7 +74,8 @@ struct ZoomableScrollView<Content: View>: UIViewControllerRepresentable {
override func viewDidLayoutSubviews() { override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews() super.viewDidLayoutSubviews()
if host.view.intrinsicContentSize != lastIntrinsicSize { if !host.view.intrinsicContentSize.equalTo(.zero),
host.view.intrinsicContentSize != lastIntrinsicSize {
self.lastIntrinsicSize = host.view.intrinsicContentSize self.lastIntrinsicSize = host.view.intrinsicContentSize
let maxHeight = view.bounds.height - view.safeAreaInsets.top - view.safeAreaInsets.bottom let maxHeight = view.bounds.height - view.safeAreaInsets.top - view.safeAreaInsets.bottom
@ -83,7 +84,6 @@ struct ZoomableScrollView<Content: View>: UIViewControllerRepresentable {
let widthScale = maxWidth / host.view.intrinsicContentSize.width let widthScale = maxWidth / host.view.intrinsicContentSize.width
let minScale = min(widthScale, heightScale) let minScale = min(widthScale, heightScale)
let maxScale = minScale >= 1 ? minScale + 2 : 2 let maxScale = minScale >= 1 ? minScale + 2 : 2
print("min: \(minScale), max: \(maxScale)")
scrollView.minimumZoomScale = minScale scrollView.minimumZoomScale = minScale
scrollView.maximumZoomScale = maxScale scrollView.maximumZoomScale = maxScale
scrollView.zoomScale = minScale scrollView.zoomScale = minScale