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?
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
}
}
}
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
Create temporary directory:
file:///private/var/mobile/Containers/Data/Application/FCD3E053-82DF-4EAE-86BA-4C65880D5B90/tmp/
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
And finally copy item to your temporary folder using FileManager
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)
}
}