damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

MediaPicker.swift (6006B)


      1 //
      2 //  ImagePicker.swift
      3 //  damus
      4 //
      5 //  Created by Swift on 3/31/23.
      6 //
      7 
      8 import UIKit
      9 import SwiftUI
     10 import PhotosUI
     11 
     12 struct MediaPicker: UIViewControllerRepresentable {
     13 
     14     @Environment(\.presentationMode)
     15     @Binding private var presentationMode
     16 
     17     @Binding var image_upload_confirm: Bool
     18     var imagesOnly: Bool = false
     19     let onMediaPicked: (PreUploadedMedia) -> Void
     20     
     21 
     22     final class Coordinator: NSObject, PHPickerViewControllerDelegate {
     23         let parent: MediaPicker
     24 
     25         init(_ parent: MediaPicker) {
     26             self.parent = parent
     27         }
     28         
     29         func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
     30             if results.isEmpty {
     31                 self.parent.presentationMode.dismiss()
     32             }
     33             
     34             for result in results {
     35                 if result.itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
     36                     result.itemProvider.loadItem(forTypeIdentifier: UTType.image.identifier, options: nil) { (item, error) in
     37                         guard let url = item as? URL else { return }
     38                         
     39                         if(url.pathExtension == "gif") {
     40                             // GIFs do not natively support location metadata (See https://superuser.com/a/556320 and https://www.w3.org/Graphics/GIF/spec-gif89a.txt)
     41                             // It is better to avoid any GPS data processing at all, as it can cause the image to be converted to JPEG.
     42                             // Therefore, we should load the file directtly and deliver it as "already processed".
     43                             
     44                             // Load the data for the GIF image
     45                             // - Don't load it as an UIImage since that can only get exported into JPEG/PNG
     46                             // - Don't load it as a file representation because it gets deleted before the upload can occur
     47                             _ = result.itemProvider.loadDataRepresentation(for: .gif, completionHandler: { imageData, error in
     48                                 guard let imageData else { return }
     49                                 let destinationURL = generateUniqueTemporaryMediaURL(fileExtension: "gif")
     50                                 do {
     51                                     try imageData.write(to: destinationURL)
     52                                     Task {
     53                                         await self.chooseMedia(.processed_image(destinationURL))
     54                                     }
     55                                 }
     56                                 catch {
     57                                     Log.error("Failed to write GIF image data from Photo picker into a local copy", for: .image_uploading)
     58                                 }
     59                             })
     60                         }
     61                         else if canGetSourceTypeFromUrl(url: url) {
     62                             // Media was not taken from camera
     63                             self.attemptAcquireResourceAndChooseMedia(
     64                                 url: url,
     65                                 fallback: processImage,
     66                                 unprocessedEnum: {.unprocessed_image($0)},
     67                                 processedEnum: {.processed_image($0)}
     68                             )
     69                         } else {
     70                             // Media was taken from camera
     71                             result.itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in
     72                                 if let image = image as? UIImage, error == nil {
     73                                     self.chooseMedia(.uiimage(image))
     74                                 }
     75                             }
     76                         }
     77                     }
     78                 } else if result.itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
     79                     result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { (url, error) in
     80                         guard let url, error == nil else { return }
     81 
     82                         self.attemptAcquireResourceAndChooseMedia(
     83                             url: url,
     84                             fallback: processVideo,
     85                             unprocessedEnum: {.unprocessed_video($0)},
     86                             processedEnum: {.processed_video($0)}
     87                         )
     88                     }
     89                 }
     90             }
     91         }
     92         
     93         private func chooseMedia(_ media: PreUploadedMedia) {
     94             self.parent.onMediaPicked(media)
     95             self.parent.image_upload_confirm = true
     96         }
     97         
     98         private func attemptAcquireResourceAndChooseMedia(url: URL, fallback: (URL) -> URL?, unprocessedEnum: (URL) -> PreUploadedMedia, processedEnum: (URL) -> PreUploadedMedia) {
     99             if url.startAccessingSecurityScopedResource() {
    100                 // Have permission from system to use url out of scope
    101                 print("Acquired permission to security scoped resource")
    102                 self.chooseMedia(unprocessedEnum(url))
    103             } else {
    104                 // Need to copy URL to non-security scoped location
    105                 guard let newUrl = fallback(url) else { return }
    106                 self.chooseMedia(processedEnum(newUrl))
    107             }
    108         }
    109 
    110     }
    111         
    112     func makeCoordinator() -> Coordinator {
    113             Coordinator(self)
    114         }
    115     
    116     func makeUIViewController(context: Context) -> PHPickerViewController {
    117         var configuration = PHPickerConfiguration(photoLibrary: .shared())
    118         configuration.selectionLimit = 1
    119         configuration.filter = imagesOnly ? .images : .any(of: [.images, .videos])
    120         
    121         let picker = PHPickerViewController(configuration: configuration)
    122         picker.delegate = context.coordinator as any PHPickerViewControllerDelegate
    123         return picker
    124     }
    125 
    126     func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
    127     }
    128 }