iOS PHPickerViewController does not provide Exif/Meta-Data - ios

I am trying to read out some metadata from images which I fetched with the PHPickerViewController.
var config = PHPickerConfiguration()
self.phPicker = PHPickerViewController(configuration: config)
if let picker = self.phPicker {
self.phPicker?.delegate = self
self.present(picker, animated: true) {
self.phPicker = nil
}
}
But the PHPickerResult.assetIdentifier to access those data is always nil.
extension ChatViewController: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
dismiss(animated: true) {
if results.isEmpty {
return
}
assert(results.first!.assetIdentifier != nil) // fails
}
}
}

The trick is the correct configuration of the PHPickerViewController.
var config = PHPickerConfiguration() // WRONG!
let picker = PHPickerViewController(configuration: config)
According to documentation you have to use the PHPhotoLibrary.
A PHPhotoLibrary provides access to the metadata and image data for
the photos, videos and related content in the user's photo library,
including content from the Camera Roll, iCloud Shared, Photo Stream,
imported, and synced from iTunes.
So just use PHPhotoLibrary in the constructor and everything works fine.
var config = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared()) // CORRECT!
let picker = PHPickerViewController(configuration: config)

Related

Clearing UserDefaults.standard.data information doesn't actually delete it?

I saved a UIImage to UserDefaults via .data with this code, where the key equals "petPhoto1":
#IBAction func addPhotoButton(_ sender: Any) {
let picker = UIImagePickerController()
picker.allowsEditing = false
picker.delegate = self
picker.mediaTypes = ["public.image"]
present(picker, animated: true)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
image.storeInUserDefaults(for: "petPhoto\(activePet)")
UserDefaults.standard.set("yes", forKey: "doesImageExist\(activePet)")
}
dismiss(animated: true, completion: nil)
}
(unrelated stuff in between)
extension UIImage {
func storeInUserDefaults(with compressionQuality: CGFloat = 0.8, for key: String) {
guard let data = self.jpegData(compressionQuality: compressionQuality) else { return }
let encodedImage = try! PropertyListEncoder().encode(data)
UserDefaults.standard.set(encodedImage, forKey: key)
}
}
Now when I erase it like this:
UserDefaults.standard.set(nil, forKey: "petPhoto1")
I can still see that "Documents & Data" for my app under Settings is still full with the same size as the original image, indicating that it didn't actually delete it, even though it no longer displays when it gets loaded back from UserDefaults.
Can anyone figure out a way to fix this? Thanks!
By the way, in case it helps, here is other code related to this issue:
The code I use in the ImageViewController that I display the image after saving it:
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
activePet = UserDefaults.standard.string(forKey: "activePet")! // activePet = 1 (confirmed with debugging with other, unrelated code
imageView.image = try? UIImage.loadFromUserDefaults(with: "petPhoto\(activePet)")
}
extension UIImage {
static func loadFromUserDefaults(with key: String) throws -> UIImage? {
let activePet = UserDefaults.standard.string(forKey: "activePet")!
guard let data = UserDefaults.standard.data(forKey: "petPhoto\(activePet)") else {
return nil
}
do {
let decodedImageData = try PropertyListDecoder().decode(Data.self, from: data)
return UIImage(data: decodedImageData)
} catch let error {
throw error
}
}
}
When you do this:
UserDefaults.standard.set(nil, forKey: "petPhoto1")
The link between the key and the file saved will be removed synchronously. that means if you try to access the value for this key, it gives nil.
But this image needs to be cleared from storage too, that will be happening asynchronously [we don't have completion handler API support from apple to get this information].
Apple Documentation for reference:
At runtime, you use UserDefaults objects to read the defaults that
your app uses from a user’s defaults database. UserDefaults caches the
information to avoid having to open the user’s defaults database each
time you need a default value. When you set a default value, it’s
changed synchronously within your process, and asynchronously to
persistent storage and other processes.
What you can try:
First approch
Give some time for the file delete operation to get completed by OS. then try to access the image in the disk.
Second approch
Try observing to the changes in the directory using GCD. Refer: https://stackoverflow.com/a/26878163/5215474

Getting video data nil when user tries to retrieve video using PHPickerViewController with selected photos permission in IOS 14, swift 5

In iOS 14 Apple has introduced PHPickerViewController where user has access to provide permission to all photo library videos or selected videos.
In first case when we provide permission to all videos,
we are able to get videos from photo library and able to convert it into the video-data to send it to backend server.
But in second case when user provide permission to selected videos,
In this scenario we are able to get the videos from the photo library ,but unable to convert it into the data from local video url.At that time data is always getting nil.
We have used below code to retrieve video from photo library url and converted it into the data.
// MARK: PHPickerViewControllerDelegate Methods
extension PhotoPickerVC: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
// Always dismiss the picker first
dismiss(animated: true)
if !results.isEmpty {
guard let itemProvider = results.first?.itemProvider else { return }
itemProvider.loadItem(forTypeIdentifier: "public.movie", options: nil) { [weak self] (fileURL, _) in
DispatchQueue.main.async {
guard let videoURL = fileURL as? URL, let _ = self else { return }
do {
//mediaURL video loading
print(videoURL)
let VideoData = try Data(contentsOf: videoURL, options: Data.ReadingOptions.alwaysMapped)
print(VideoData)
} catch _ {
print("Received nil VideoData")
}
}
}
}
}
}
This line is wrong:
itemProvider.loadItem(forTypeIdentifier: "public.movie", options: nil) { [weak self] (fileURL, _) in
You should not be trying to load any item. You can't hold a video in memory! You should be asking the provider to save the data to disk. I use this sort of code:
let movie = UTType.movie.identifier
itemProvider.loadFileRepresentation(forTypeIdentifier: movie) { url, err in
Note that you must immediately retrieve the URL, because this is a temporary location and the file will be deleted. If you want to preserve the file on disk, you must copy it off synchronously (on a background thead) to somewhere else.
Similarly, do not read the data from the file directly into memory. You can play the video from disk once you have preserved it; that's what it is for.
You can use like this type of
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
guard let provider = results.first?.itemProvider else { return }
provider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { url, err in
if let url = url {
print("video url \(url)")
}
}
if provider.canLoadObject(ofClass: UIImage.self) {
provider.loadObject(ofClass: UIImage.self) { image, _ in
self.parent.image = image as? UIImage
}
}
}

How to retrieve PHAsset from PHPicker?

In WWDC20 apple introduced PHPicker - the modern replacement for UIImagePickerController.
I'm wondering if it's possible to retrieve PHAsset using the new photo picker?
Here is my code:
private func presentPicker(filter: PHPickerFilter) {
var configuration = PHPickerConfiguration()
configuration.filter = filter
configuration.selectionLimit = 0
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
dismiss(animated: true)
}
I managed to find an answer from the developers of this framework on the apple forum:
Yes, PHPickerResult has the assetIdentifier property which can contain
a local identifier to fetch the PHAsset from the library. To have
PHPicker return asset identifiers, you need to initialize
PHPickerConfiguration with the library.
Please note that PHPicker does not extend the Limited Photos Library
access for the selected items if the user put your app in Limited
Photos Library mode. It would be a good opportunity to reconsider if
the app really needs direct Photos Library access or can work with
just the image and video data. But that really depend on the app.
The relevant section of the "Meet the new Photos picker" session
begins at 10m 20s.
Sample code for PhotoKit access looks like this:
import UIKit
import PhotosUI
class PhotoKitPickerViewController: UIViewController, PHPickerViewControllerDelegate {
#IBAction func presentPicker(_ sender: Any) {
let photoLibrary = PHPhotoLibrary.shared()
let configuration = PHPickerConfiguration(photoLibrary: photoLibrary)
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
let identifiers = results.compactMap(\.assetIdentifier)
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: identifiers, options: nil)
// TODO: Do something with the fetch result if you have Photos Library access
}
}

By UIImagePickerController we can access and upload images. How to access and upload documents from iphone?

I'm developing a loan related app where user upload his documents like pay-slips, it-returns, etc. For that I should show the user all the documents he/she having in his/her iPhone. How to show a picker for documents?
UIDocumentPickerViewController is what you are looking for.
You init one with a list of document types you want to be able to pick, and a mode, which is usually .open to get access to a file in a cloud provider directly. You can also use .import which will copy the file to your container instead of giving you access to the file in the cloud provider's container directly, if the goal is just to upload it (you can remove the copy after uploading).
Once you have created your picker, you present it, and implement the delegate method didPickDocumentsAt to retrieve the list of files chosen by the user.
Check out the Particles sample code and this years WWDC session « Managing Documents in your iOS Apps »
just call openDocumentPicker method when you want to upload document in your application..
import MobileCoreServices
func openDocumentPicker() {
let importMenu = UIDocumentMenuViewController(documentTypes: [kUTTypePDF as String], in: .import)
importMenu.delegate = self
self.present(importMenu, animated: true, completion: nil)
}
create your viewcontroller extension
extension ViewController: UIDocumentMenuDelegate {
func documentMenu(_ documentMenu: UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController) {
documentPicker.delegate = self
present(documentPicker, animated: true, completion: nil)
}
func documentMenuWasCancelled(_ documentMenu: UIDocumentMenuViewController) {
print("we cancelled")
dismiss(animated: true, completion: nil)
}
}
extension ViewController: UIDocumentPickerDelegate {
internal func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
do {
let fileAttributes = try FileManager.default.attributesOfItem(atPath: url.path)
let fileSizeNumber = fileAttributes[FileAttributeKey.size] as! NSNumber
let fileSizea = fileSizeNumber.doubleValue
let fileSize = fileSizea/1000000.0
if fileSize > 5.0 {
appDelegate.displayAlert(Title: "", Message: "Selected File are too big,Please select file less than 5.0 mb")
} else {
let documentData = try Data(contentsOf: url, options: .dataReadingMapped)
}
} catch let error {
print(error.localizedDescription)
}
}
}
above this code only you can access pdf if you want to access anpther document than just use this code
/*
let pdf = String(kUTTypePDF)
let spreadsheet = String(kUTTypeSpreadsheet)
let movie = String(kUTTypeMovie)
let aviMovie = String(kUTTypeAVIMovie)
let docs = String(kUTTypeCompositeContent)
let img = String(kUTTypeImage)
let png = String(kUTTypePNG)
let jpeg = String(kUTTypeJPEG)
let txt = String(kUTTypeText)
let zip = String(kUTTypeZipArchive)
let msg1 = String(kUTTypeEmailMessage)
let msg2 = String(kUTTypeMessage)
let types = [pdf, spreadsheet, movie, aviMovie, img, png, jpeg, txt, docs, zip, msg1, msg2]
*/
iOS 11 or later
let importMenu = UIDocumentPickerViewController.init(documentTypes: ["public.item"], in: UIDocumentPickerMode.import)
self.present(importMenu, animated: true) {
}
Please Refer the following link for more description
https://www.techotopia.com/index.php/An_iOS_Document_Browser_Tutorial
iOS 10 or earlier
You can write the below function when your document picker opens. it will work for all the file types which you want to upload.
func openDocumentPicker() {
let importMenu = UIDocumentMenuViewController(documentTypes: ["public.item"], in: .import)
importMenu.delegate = self
importMenu.modalPresentationStyle = .formSheet
self.present(importMenu, animated: true, completion: nil)
}

Select Multiple Images (UIImagePickerController or Photos.app Share UI)

In iPhone OS 3.0, Apple added the ability to share multiple pictures at once using the "Share" button and selecting multiple images (where a checkmark is used).
I'd love to have a UIImagePickerController which lets the user select multiple images at once, rather than having to go one by one. Is there a way to do this, or do I have to wait until they add this feature?
If you are supporting only iOS 14 and up, you can use Apple's PHPickerViewController. It allows multiple image selection (while UIImagePickerController does not).
An additional benefit to using PHPickerViewController vs other libraries listed above is that the user will not need to grant permission to access your photo library.
Try this wonderful API in swift: ImagePicker. As all other image APIs, it is simple to use and it is very well updated.
1.install pod - pod "BSImagePicker", "~> 2.8"
inside info plist add row Privacy - Photo Library Usage Description
3.paste below code inside a .swift file-
import UIKit
import BSImagePicker
import Photos
class MultipleImgViC: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var SelectedAssets = [PHAsset]()
var photoArray = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func selectImages(_ sender: Any) {
let vc = BSImagePickerViewController()
self.bs_presentImagePickerController(vc, animated: true, select: { (assest: PHAsset) -> Void in
},
deselect: { (assest: PHAsset) -> Void in
}, cancel: { (assest: [PHAsset]) -> Void in
}, finish: { (assest: [PHAsset]) -> Void in
for i in 0..<assest.count
{
self.SelectedAssets.append(assest[i])
}
self.convertAssetToImages()
}, completion: nil)
}
#IBAction func dismissview(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
extension MultipleImgViC{
func convertAssetToImages() -> Void {
if SelectedAssets.count != 0{
for i in 0..<SelectedAssets.count{
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
var thumbnail = UIImage()
option.isSynchronous = true
manager.requestImage(for: SelectedAssets[i], targetSize: CGSize(width: 200, height: 200), contentMode: .aspectFill, options: option, resultHandler: {(result,info) -> Void in
thumbnail = result!
})
let data = thumbnail.jpegData(compressionQuality: 0.7)
let newImage = UIImage(data: data!)
self.photoArray.append(newImage! as UIImage)
}
self.imageView.animationImages = self.photoArray
self.imageView.animationDuration = 3.0
self.imageView.startAnimating()
}
}
}
Note :- if pod file give "How to fix “SWIFT_VERSION '3.0' is unsupported, supported versions are: 4.0, 4.2, 5.0” error in Xcode 10.2?
" this error then solve it from this link:- https://stackoverflow.com/a/55901964/8537648
video reference: - https://youtu.be/B1DelPi1L0U
sample image:-
AssetLibrary + UICollectionView ^^
Basically, with StoryBoard, you import aUINavigationController, you change the root controller to anUICollectionViewController (will be your Album list), end add anotherUICollectionViewController (will be your photos list).
Then with Assetlibrary you retrieve user albums and user album content.
I will make a such component as soon as i have some time.
You can use this OpalImagePicker like this (Swift 4):
var imagePicker: OpalImagePickerController!
imagePicker = OpalImagePickerController()
imagePicker.imagePickerDelegate = self
imagePicker.selectionImage = UIImage(named: "aCheckImg")
imagePicker.maximumSelectionsAllowed = 3 // Number of selected images
present(imagePicker, animated: true, completion: nil)
And then implement its delegate:
func imagePickerDidCancel(_ picker: OpalImagePickerController) {
//Cancel action
}
func imagePicker(_ picker: OpalImagePickerController, didFinishPickingImages images: [UIImage]) {
}
No need for a 3rd party library. You can use PHPickerViewController.
https://developer.apple.com/documentation/photokit/phpickerviewcontroller
private func showImagePicker() {
var configuration = PHPickerConfiguration()
configuration.selectionLimit = 5 // Selection limit. Set to 0 for unlimited.
configuration.filter = .images // he types of media that can be get.
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
}
Apple introduced PHPickerViewController in iOS14.
Advantages
No additional permission need to be implemented (its private by default)
Supports Multi-selection (limit can also be specified)
Zooming and previewing the selection
Documentation - https://developer.apple.com/documentation/photokit/phpickerviewcontroller
Video - https://developer.apple.com/videos/play/wwdc2020/10652/
Hope this helps!
How about this way:
Open "photos.app" first, select multiple photos , and copy them ;
In your own app, try to retrieve those copies photos;
I knew that there are some apps did like this, but do not know how can achieve step 2.

Resources