Search code examples
iosswiftalamofireuiimagepickercontrollerios13

iOS 13 adds "trim." prefix to files selected from UIImagePickController


iOS 13 adds a trim. prefix when selecting a file from Photos:

file:///private/var/mobile/Containers/Data/PluginKitPlugin/FPDLKFHEQ-4T56-3456-HTE2-39EK2KDJUR/tmp/trim.DFLSPD0F-32RE-UYI8-DFHA-DPFLEOW098UH.MOV

Before iOS-13 was like this:

file:///private/var/mobile/Containers/Data/PluginKitPlugin/FPDLKFHEQ-4T56-3456-HTE2-39EK2KDJUR/tmp/DFLSPD0F-32RE-UYI8-DFHA-DPFLEOW098UH.MOV

This is an issue when passing the file URL to alamofire to upload the file to a backend server. It causes an "unknown error" and the upload fails. Perhaps alamofire is having trouble with that little prefix?

Is there any solution for this?


Solution

  • Yes, here people have faced the same issue. I will extend the answer.

    You probably need to copy that video into the temporary folder, so in your UIImagePickerControllerDelegate's method:

    public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
        if let mediaType = info[.mediaType] as? String,
            mediaType == "public.movie",
            let trimVideoURL = info[.mediaURL] as? URL {
            if #available(iOS 13, *) {
                // 1
                let urlSlices = trimVideoURL.relativeString.split(separator: ".")
                // 2
                let tempDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
                // 3
                let targetFileURL = tempDirectoryURL.appendingPathComponent(String(urlSlices[1])).appendingPathExtension(String(urlSlices[2]))
                do {
                    // 4
                    try FileManager.default.copyItem(at: trimVideoURL, to: targetFileURL)
                } catch {
                    print(error.localizedDescription)
                }
                // use the video file via targetFileURL path
            }
        }
    }
    
    1. Here you have trimVideURL, that needs to be sliced by . file:///private/var/mobile/Containers/Data/PluginKitPlugin/ECD6D7FD-35A8-47E2-8323-808454A32C37/tmp/trim.919BDBB7-E711-49AA-BB84-89E9B0416045.MOV

    2. Create temporary directory:
      file:///private/var/mobile/Containers/Data/Application/FCD3E053-82DF-4EAE-86BA-4C65880D5B90/tmp/

    3. Compose the target filename in temporary folder: file:///private/var/mobile/Containers/Data/Application/FCD3E053-82DF-4EAE-86BA-4C65880D5B90/tmp/919BDBB7-E711-49AA-BB84-89E9B0416045.MOV

    4. And finally copy item to your temporary folder using FileManager

    5. And when you ready, you could delete the file using:

    do {
        try FileManager.default.removeItem(at: targetFileURL)
    } catch {
        print(error.localizedDescription)
    }
    

    Good article about temporary files

    Also, you could use this test project as a reference, just copy it into new created project to test out functionality:

    import UIKit
    import WebKit
    
    class ViewController: UIViewController, UINavigationControllerDelegate {
        var imageView: UIImageView!
        var videoView: WKWebView!
        var selectButton: UIButton!
        var deleteButton: UIButton!
    
        var pickerController: UIImagePickerController?
        var targetFileURL: URL?
    
        override func viewDidLoad() {
            super.viewDidLoad()
            configureUI()
            setupPickerController()
        }
    
        func configureUI() {
            imageView = UIImageView()
            imageView.translatesAutoresizingMaskIntoConstraints = false
            videoView = WKWebView()
            videoView.translatesAutoresizingMaskIntoConstraints = false
            selectButton = UIButton(type: .system)
            selectButton.setTitle("Select", for: .normal)
            selectButton.addTarget(self, action: #selector(showImagePicker), for: .touchUpInside)
            selectButton.translatesAutoresizingMaskIntoConstraints = false
            deleteButton = UIButton(type: .system)
            deleteButton.setTitle("Delete", for: .normal)
            deleteButton.addTarget(self, action: #selector(deleteCurrentTempFile), for: .touchUpInside)
            deleteButton.translatesAutoresizingMaskIntoConstraints = false
    
            view.addSubview(imageView)
            view.addSubview(videoView)
            view.addSubview(selectButton)
            view.addSubview(deleteButton)
    
            imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
            imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
            imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
            imageView.heightAnchor.constraint(equalToConstant: 250.0).isActive = true
    
            videoView.topAnchor.constraint(equalTo: imageView.bottomAnchor).isActive = true
            videoView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
            videoView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
            videoView.heightAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true
    
            deleteButton.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
            deleteButton.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
            deleteButton.heightAnchor.constraint(equalToConstant: 60.0).isActive = true
            deleteButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
    
            selectButton.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
            selectButton.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
            selectButton.heightAnchor.constraint(equalTo: deleteButton.heightAnchor).isActive = true
            selectButton.bottomAnchor.constraint(equalTo: deleteButton.topAnchor).isActive = true
    
            view.layoutIfNeeded()
        }
    
        func setupPickerController() {
            self.pickerController = UIImagePickerController()
            self.pickerController?.delegate = self
            if let mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary) {
                self.pickerController?.mediaTypes = mediaTypes
            }
        }
    
        private func action(for type: UIImagePickerController.SourceType, title: String) -> UIAlertAction? {
            guard UIImagePickerController.isSourceTypeAvailable(type) else {
                return nil
            }
    
            return UIAlertAction(title: title, style: .default) { [unowned self] _ in
                self.pickerController?.sourceType = type
                if let pickerController = self.pickerController {
                    self.present(pickerController, animated: true)
                }
            }
        }
    
        @IBAction func showImagePicker() {
            let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
            if let action = self.action(for: .photoLibrary, title: "Photo library") {
                alertController.addAction(action)
            }
            alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
            present(alertController, animated: true)
        }
    
        @IBAction func deleteCurrentTempFile() {
            if let url = targetFileURL,
                FileManager.default.fileExists(atPath: url.path) {
                removeTemporaryFile(at: url)
            }
        }
    
        func didSelectMedia(image: UIImage?, videoURL: URL?) {
            if let image = image {
                imageView.image = image
            } else if let videoURL = videoURL {
                let request = URLRequest(url: videoURL)
                videoView.load(request)
                targetFileURL = videoURL
            }
        }
    
        // remove it when you done
        func removeTemporaryFile(at url: URL) {
            do {
                try FileManager.default.removeItem(at: url)
            } catch {
                print(error.localizedDescription)
            }
            targetFileURL = nil
        }
    }
    
    extension ViewController: UIImagePickerControllerDelegate {
    
        public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
            if let mediaType = info[.mediaType] as? String {
                if mediaType == "public.movie", let trimVideoURL = info[.mediaURL] as? URL {
                    if #available(iOS 13, *) {
                        let urlSlices = trimVideoURL.relativeString.split(separator: ".")
                        let tempDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
                        let targetFileURL = tempDirectoryURL.appendingPathComponent(String(urlSlices[1])).appendingPathExtension(String(urlSlices[2]))
                        do {
                            try FileManager.default.copyItem(at: trimVideoURL, to: targetFileURL)
                        } catch {
                            print(error.localizedDescription)
                        }
                        self.didSelectMedia(image: nil, videoURL: targetFileURL)
                    } else {
                        self.didSelectMedia(image: nil, videoURL: trimVideoURL)
                    }
                } else if mediaType == "public.image", let image = info[.originalImage] as? UIImage {
                    self.didSelectMedia(image: image, videoURL: nil)
                }
            }
            picker.dismiss(animated: true, completion: nil)
        }
    }