How to get image url from PHAsset for iOS 13 - swift4

As no longer PHImageFileURLKey is given, how i can get image reference url for picked image!
Using PHImageFileUTIKey key i was able to get the file name.
I tried to play around PHAssetResource.assetResources(for: assets[0]) but still there is no luck!

asset.requestContentEditingInput(with: nil, completionHandler: { (input, info) in
if let input = input {
print(input.fullSizeImageURL) // file:///xxx
}
})
this is how i was able to get full imageURL.

Related

Link app object to file on disk with metadata

Following this topic : iOS PDFkit cannot add custom attribute to pdf document
My app is using PDFKit to save files
I'm trying to set custom key metadata to PDFDocument I save on the device.
The object in my app ('Test') has two important properties :
id: a UUID to be able to retrieve the file on disk (the linked file on disk URL is this_UUID.jpg).
name: a human-readable string set by the user.
This cause some problems :
the file name is a UUID not human readable, so it's bad user experience.
If the user renames the file, the app won't be able to get the file.
So the id is to have a human-readable label for the file. So when the user opens the File app he can find it easily. And add metadata with the id so my app can retrieve it even if renamed. Looks like a nice solution right?
// First I create my own attribute
fileprivate extension PDFDocumentAttribute {
static let testId = PDFDocumentAttribute(rawValue: "com.sc.testID")
}
// Then I set the news attributes before saving the file, in the 'test' class
func setDocument(pdf: PDFDocument) {
let fileURL = self.getPDFDocumentURL()
print("the metadata is \(pdf.documentAttributes)") // print an empty dictionary
pdf.documentAttributes?[PDFDocumentAttribute.testId] = self.id
pdf.documentAttributes?[PDFDocumentAttribute.titleAttribute] = self.name // I suppose the ddisplay name of the document ? It's not, so what is that ?
print("the metadata is now \(pdf.documentAttributes)") // both are printed, it looks ok
//pdf.write(to: fileURL) // I tested this one too, same issues
let data = pdf.dataRepresentation()
do {
try data?.write(to: fileURL, options: .completeFileProtection)
} catch {
print(error.localizedDescription)
}
}
From here it looks ok, when I want to retrieve the pdf document I will check in the folder the id of each doc and return the doc when id match. But the problem is when I get the documentAttributes the attribute 'testId' isn't in. Note the native title, is set correctly.
So I could get the id from there but that looks pretty inappropriate
//still in 'Test' class
func getPDFDocument() -> PDFDocument? {
// get all docs in the folder ad check metadata for each
let fileManager = FileManager.default
let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
do {
let fileURLs = try fileManager.contentsOfDirectory(at: SchoolTest.getSubjectFolderURL(subject: self.subject!), includingPropertiesForKeys: nil)
for url in fileURLs {
print("the doc attributes are : \(PDFDocument(url: url)?.documentAttributes)") // contain title and others preset by Apple but not my custom 'testId'
if let doc = PDFDocument(url: url), doc.documentAttributes?[PDFDocumentAttribute.titleAttribute/*testId*/] as? String == self.documentName {
return doc // this work temporary
}
}
} catch {
print("Error while enumerating files \(documentsURL.path): \(error.localizedDescription)")
}
return nil
}
Display name:
Currently, the display name/label displayed in the File app is the file name (from URL).
This can cause problems too because if two 'Test' have the same name, their linked file gonna have the same URL. So when the most recent one will be saved on disk it will overwrite the other.
That's a problem I don't have when using the 'Test' id property for the file URL.
If I could set a display name for the file and keep the URL with the UUID that should resolve the problem.
Directories have the same localizing issue, I have named them with English but Apple native directories are localized. A localized name could be nice for user experience.
After hours of research, I can't find a way to localize or apply a display name to my files/directories.
// I tried that without positive result
var url = fileURL as NSURL
try url.setResourceValue("localized label", forKey: .localizedLabelKey)
print("localized name is \(try url.resourceValues(forKeys: [.localizedLabelKey]))")
let newURL = url as URL
try data?.write(to: newURL, options: .completeFileProtection)
Am I doing something badly? How should we do when adding custom metada to a file?

Swift: Get the correct file size for JPEG image

I have a UIImage object, say from the camera roll via PHAsset. The image is saved as a .jpg file:
asset.requestContentEditingInput(with: nil) { (input, nil) in
print(input?.fullSizeImageURL) // somefile.jpg
}
To get the file size should not data.count from this return the correct file size in bytes?
PHImageManager.default().requestImageData(for: asset, options: nil) { data, _, _, _ in
if let _data = data {
print(_data.count) // 6759240
}
}
The output for a particular image is 6759240 while fileSize() returns 2978548.0 (which is the right file size) bytes.
func fileSize(forURL url: Any) -> Double {
var fileURL: URL?
var fileSize: Double = 0.0
if (url is URL) || (url is String)
{
if (url is URL) {
fileURL = url as? URL
}
else {
fileURL = URL(fileURLWithPath: url as! String)
}
var fileSizeValue = 0.0
try? fileSizeValue = (fileURL?.resourceValues(forKeys: [URLResourceKey.fileSizeKey]).allValues.first?.value as! Double?)!
if fileSizeValue > 0.0 {
fileSize = (Double(fileSizeValue))
}
}
return fileSize
}
Does it mean someUIImage?.jpegData(compressionQuality: 1)?.count does not return the correct size of JPEG image file (if saved)?
One more thing, Is there any way to determine the image file size before writing it on the disk?
All of these is to compare the file size between the original and compressed image.
This sounds like a misunderstanding of what the various terms and calls refer to.
You have no direct access to a file stored in the user's Photo library. There may in fact be no such file; you should make no assumptions about the storage format. When you ask PHImageManager for an image's data, you are given the bitmap data, ready for use. Thus you should expect this data to be big, in exact proportion to the dimensions of the image. 6759240 is more than 6MB, which sounds about right on an older iPhone; a newer iPhone, takes 4032x3024 photos which is more than 8MB.
Then in a different part of your code you call fileSize(forURL:). Now you're looking at an actual file, in the file system, in a place where you can access it. If this is an image file, it is compressed; just how much it is compressed depends on the format. 2978548 is about 3MB which is pretty good for a JPEG compressed without too much lossiness.
Finally, you ask about UIImage jpegData(compressionQuality: 1)?.count. You do not show any code that actually calls that. But this is data ready for saving as a file directly with write(to:) and a URL, and I would expect it to be the same as fileSize(forURL:) if you were to check the very same file later.

Load UIImage from PHAsset Image absoluteString Swift 4

I am saving a list of file names/paths so I can load the image at a later time to upload it.
When the user selects the images from the camera roll, I get back this
file:///Users/admin/Library/Developer/CoreSimulator/Devices/B31CE61D-FB46-41F0-B254-B66B9335E1E4/data/Media/DCIM/100APPLE/IMG_0005.JPG
But when I try to load up the image,
if let image = UIImage(named: filepath) {
imageView.image = image
}
It doesn't load.
How do I load an image from a filepath?
The code I use to get the file path
func getURL(ofPhotoWith mPhasset: PHAsset, completionHandler : #escaping ((_ responseURL : URL?) -> Void)) {
let options = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = {(adjustmeta: PHAdjustmentData) -> Bool in
return true
}
mPhasset.requestContentEditingInput(with: options, completionHandler: { (contentEditingInput, info) in
completionHandler(contentEditingInput!.fullSizeImageURL)
})
}
func add(images: [PHAsset]) {
for image in images {
getURL(ofPhotoWith: image) { (imgURL) in
if let imgURL = imgURL {
print ("ImageURL: \(imgURL.absoluteString)")
}
}
}
}
I am saving a list of file names/paths so I can load the image at a later time to upload it.
PHContentEditingInput is the wrong tool for that job. As the names of that class and the functions you're using to get one suggest, it's for content editing — tasks like applying a filter to an asset in the library.
When PHContentEditingInput gives you a file URL, it's granting you temporary access to that file. PhotoKit makes no guarantee that the asset in question will always be backed by a file at that URL, and even if it is, PhotoKit revokes temporary access to that URL when the owning PHContentEditingInput is deallocated.
A user's Photos library isn't a directory full of image files — it's a database, where each asset can have data resources stored in one or more files, which might or might not even be in local storage at all times. If you want to upload assets to an external service and preserve all the original data, you need an API that's meant for getting data resources. PhotoKit gives you two choices for that:
If you want just some image representation of the current state of the asset, use PHImageManager. This downloads and/or generates image data ready for you to save as a file, incorporating whatever edits the user has already applied to the asset:
let options = PHImageRequestOptions()
options.deliveryMode = .highQualityFormat
options.isNetworkAccessAllowed = true
PHImageManager.default().requestImageData(for: myAsset, options: options) { data, uti, orientation, info in
// save `data` to file / upload to web service
// use `uti` to find out what file type it is
}
If you want the original image data resources — that is, enough data that you could back up and restore the asset, including features like in-progress edits, Live Photo modes, and RAW format image data — use PHAssetResource and PHAssetResourceManager:
let resources = PHAssetResource.resources(for: myAsset)
let options = PHAssetResourceRequestOptions()
options.isNetworkAccessAllowed = true
for resource in resources {
let outputURL = myOutputDirectory.appendingPathComponent(resource.originalFilename)
PHAssetResourceManager.default().writeData(for: resource, to: outputURL, options: options) { error in
// handle error if not nil
}
}
I am saving a list of file names/paths so I can load the image at a later time to upload it when the user selects the images from the camera roll
Don't. The thing to save so that you can retrieve something from the camera roll at a later time is the PHAsset's localIdentifier. You can use that to get the same asset again later, and now you can ask for the associated image.

Security Scoped Bookmark - bookmark resolves but still can't access the file

EDIT: Additional information added at the bottom
I have a sandboxed, document based application that loads a user selected quicktime movie into an AVPlayer, and everything was working perfectly.
Now I am upgrading the code so that it will use Security Scoped bookmarks to get the URL rather than just storing a URL string so that the persistent store will allow the movie to be loaded upon relaunch of the application. When the bookmark is created it is stored in a Data variable of a managed object.
For some reason, this has broken the AVPlayer. While I have created a bookmark from the user selected URL, and can resolving the URL from the bookmark when the application is relaunched, the movie is not getting loaded into the AVPlayer correctly and I can't figure out why... I have confirmed that the URL being resolved from the bookmark does point to the movie file.
I have also added the appropriate entitlements to the project.
Here is my code:
Function Where User Selects a Movie To Load and Bookmark is Created
#IBAction func loadMovie(_ sender: Any) {
let openPanel = NSOpenPanel()
openPanel.title = "Select Video File To Import"
openPanel.allowedFileTypes = ["mov", "avi", "mp4"]
openPanel.begin { (result: NSApplication.ModalResponse) -> Void in
if result == NSApplication.ModalResponse.OK {
self.movieURL = openPanel.url
self.player = AVPlayer.init(url: self.movieURL!)
self.setupMovie()
if self.loadedMovieDatabase.count > 0 {
print("Movie Object Exists. Adding URL String")
self.loadedMovieDatabase[0].urlString = String(describing: self.movieURL!)
} else {
print("No Movie Object Exists Yet. Creating one and adding URL String")
let document = NSDocumentController.shared.currentDocument as! NSPersistentDocument
let myManagedObjectContext = document.managedObjectContext!
let newMovie = NSEntityDescription.insertNewObject(forEntityName: "Movie", into: myManagedObjectContext) as! MovieMO
self.loadedMovieDatabase.append(newMovie)
self.loadedMovieDatabase[0].urlString = String(describing: self.movieURL!)
}
// create Security-Scoped bookmark - Added 2/1/18
do {
try self.loadedMovieDatabase[0].bookmark = (self.movieURL?.bookmarkData(options: NSURL.BookmarkCreationOptions.withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil))!
} catch {
print("Can't create security bookmark!")
}
}
}
}
Function where Bookmark is Resolved into URL and Movie is Loaded
// initialize AVPlayer with URL stored in coreData movie object if it exists and is a valid path
if loadedMovieDatabase.count > 0 {
// initialize with saved movie path if it is valid (from security bookmark data)
// let myURL = URL(string: loadedMovieDatabase[0].urlString!) <- replaced with new code below
print("Loading URL from Bookmark")
var urlResult = false
var myURL : URL
do {
try myURL = URL.init(resolvingBookmarkData: loadedMovieDatabase[0].bookmark, bookmarkDataIsStale: &urlResult)!
print("URL Loaded from Bookmark")
print("URL is", myURL)
let isSecuredURL = myURL.startAccessingSecurityScopedResource()
print("IsSecured = ", isSecuredURL)
player = AVPlayer.init(url: myURL)
print("Setting Up Movie")
setupMovie()
} catch {
// No Data in bookmark so load default ColorBars movie instead
print("No Security Bookmark Available. Reverting to Default Color Bars")
let myURL = URL(string: initialMoviePath)
player = AVPlayer.init(url: myURL!)
setupMovie()
}
} else {
// load default ColorBars movie instead
print("Nothing was loaded so just set up a new document.")
let myURL = URL(string: initialMoviePath)
player = AVPlayer.init(url: myURL!)
setupMovie()
}
I am new to Security-Scoped Bookmarks, so I'm hoping that this may be obvious to anyone who has worked with them before.
I'm wondering if it's a problem with:
let isSecuredURL = myURL.startAccessingSecurityScopedResource()
Perhaps I'm calling this incorrectly? Sometimes I find Apple's documentation to be vague and confusing... Any insight would be appreciated!
EDIT:
I believe I know why this is happening, but I'm not sure how to fix it...
myURL.startAccessingSecurityScopedResource()
always returns FALSE... per the documentation that would mean that it's not working. Additionally, while the movie file is located on my Desktop, the Resolved URL comes up as the following (this may be normal, I don't know.):
file:///Users/me/Library/Containers/myapp/Data/Desktop/sample_on_desktop.mov
The apple docs make reference to the fact that a Document Scope can not use files in the system (aka "/Library"), but my entitlements are setup to use application-scope bookmarks, and my bookmark was created using the nil flag for relativeURL: so this shouldn't be an issue.
I just stumbled upon the answer accidentally...
For starters, when I was resolving the URL, I was not using the method which allows you to include OPTIONS, so my URL was resolved WITHOUT the security-scope. My original code to resolve was:
try myURL = URL.init(resolvingBookmarkData: loadedMovieDatabase[0].bookmark, bookmarkDataIsStable: &urlResult)!
When I should have been using the version with options here:
try myURL = URL.init(resolvingBookmarkData: loadedMovieDatabase[0].bookmark, Options: URL.bookmarkResolutionOptions.withSecurityScope, relativeTo: nil, bookmarkDataIsStable: &urlResult)!
Basically, I used the first init option Xcode presented in the predictive list with the words "resolvingBookmarkData:" when I should have looked further down the list. (This is how I found my error.)
NOTE also that it's important to use...
URL.bookmarkResolutionOptions.withSecurityScope
and not
URL.bookmarkCreationOptions.withSecurityScope
...when you're resolving your URL or it doesn't appear to work correctly.
Thus ends my frustration with this problem :) I hope this explanation might help others facing this problem!

Finder Share Extension: Getting a preview image from NSItemProvider

I'm creating a Finder Share extension to be used when selecting a file. I'd like to show an image preview of the file that you are sharing (like you see in the Messages and Twitter Finder Share extensions, for example).
In the loadView method of the view controller for my share extension, I'm doing this:
let item = self.extensionContext!.inputItems[0] as! NSExtensionItem
if let attachments = item.attachments as? [NSItemProvider] {
if let attachment = attachments.first {
attachment.loadPreviewImage(options: nil, completionHandler: { (item, error) in
if error != nil {
//handle error...
} else if let img = item as? NSImage {
//put image preview in my "share sheet"...
}
})
}
}
The problem is that when I keep hitting the error condition. The error that I'm getting is:
Error Domain=NSItemProviderErrorDomain Code=-1000 "Cannot load preview." UserInfo={NSLocalizedDescription=Cannot load preview.}
For what it's worth, I am able to call attachment.loadItem() successfully and work with the file. But I don't really care to do anything with the file itself at this point, I just want a thumbnail image that represents the file which theoretically this method should give me...
Any ideas?
I am in the same situation. After read the manual, I think here is the reason.
"Loads the preview image for the item that is represented by the item provider."
However, it is not forced that the item provider must provides a preview. So the error simply says that there is no preview that the item provider can provide. You should do it yourself.