i want to get some extra info about the images i'll share with the Share extension. I can create the UIImage from the url but when i want to obtain an ALAsset i get nil. Anyone had this problem?
itemProvider!.loadItemForTypeIdentifier(String(kUTTypeImage), options: nil, completionHandler: { (decoder: NSSecureCoding!, error: NSError!) -> Void in
if ALAssetsLibrary.authorizationStatus() == ALAuthorizationStatus.Authorized {
if let url = decoder as? NSURL {
ALAssetsLibrary().assetForURL(url, resultBlock: { (myasset:ALAsset!) -> Void in
println(url)
println(fm.fileExistsAtPath(url.path!))
println(myasset)
let location = myasset?.valueForProperty(ALAssetPropertyLocation) as CLLocation?
let date = myasset?.valueForProperty(ALAssetPropertyDate) as NSDate?
self.extensionContext?.completeRequestReturningItems([AnyObject](), completionHandler: nil)
}, failureBlock: { (myerror:NSError!) -> Void in
})
}
}
The output is
file:///var/mobile/Media/DCIM/102APPLE/IMG_2977.JPG
true
nil
the immediate issue is you are passing a file url in place of an asset url for this line: ALAssetsLibrary().assetForURL(url, resultBlock: { (myasset:ALAsset!) -> Void in.
Share extensions return the url to the path on the iphone's file system...something of the form: file:///..... These are not the same as the urls that an ALAsset require in the assetForURL method.
Unfortunately, though this makes the code more correct, it still doesn't fix the issue. I spent some time with many different approaches. Writing a new image to disk via the AssetsLibrary and the given file path will return an asset url upon completion which will work successfully - though you obviously don't want duplicate photos in your camera roll. (Note: there is no way to delete an ALAsset). You could probably hold onto the file path and delete the new image when you are done with it, but that is an extremely messy approach.
I ended up rewriting my approach given these limitations.
Related
I'm storing images and videos in a Camera Roll album using PhotoKit, and want to allow the user to share them using UIActivityViewController. If I pass UIActivityViewController a UIImage instance, it works as expected, probably because the image data is passed in memory. However, videos need to be passed by URL because there's no video analogue to UIImage. When I pass a URL to a video, I get an error "Could not create sandbox extension". If I pass a URL to an image, I get a similar error.
Based on this, it seems as though I might be able to get around this error by exporting the assets to the Documents directory, and passing UIActivityViewController the URL to the asset in Documents. However, I've read elsewhere that the Camera Roll can serve a similar purpose, and it goes to reason that the Camera Roll would be one of the few places that can hold data for sharing between apps.
Is there a way to pass UIActivityViewController URLs to Camera Roll assets without copying them to Documents? Is there a better way to be sharing images and video that are already in Camera Roll?
Implementation Details:
I'm generating URLs for assets using this:
func videoFor(asset: PHAsset, resultHander: #escaping (AVAsset?, AVAudioMix?, [AnyHashable : Any]?) -> Void) {
imageManager.requestAVAsset(forVideo: asset, options: nil, resultHandler: resultHander)
}
func urlFor(asset: PHAsset, resultHandler: #escaping (URL?) -> Void) {
if ( asset.mediaType == .video ) {
videoFor(asset: asset) { (asset, audioMix, info) in
let asset = asset as! AVURLAsset
resultHandler(asset.url)
}
}
else if ( asset.mediaType == .image ) {
let options: PHContentEditingInputRequestOptions = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = {(adjustmeta: PHAdjustmentData) -> Bool in
return true
}
asset.requestContentEditingInput(with: options, completionHandler: {(contentEditingInput: PHContentEditingInput?, info: [AnyHashable : Any]) -> Void in
resultHandler(contentEditingInput!.fullSizeImageURL as URL?)
})
}
else {
resultHandler(nil)
}
}
Here is the full error I get in console when trying to share an image by URL:
Failed to determine whether URL /var/mobile/Media/DCIM/100APPLE/IMG_0201.JPG (n) is managed by a file provider
Could not create sandbox extension. Error: Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedDescription=Could not create sandbox extension of type com.apple.app-sandbox.read for URL /var/mobile/Media/DCIM/100APPLE/IMG_0201.JPG. Error: No such file or directory}
... and for a video:
Failed to determine whether URL /var/mobile/Media/DCIM/100APPLE/IMG_0202.M4V (n) is managed by a file provider
Could not create sandbox extension. Error: Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedDescription=Could not create sandbox extension of type com.apple.app-sandbox.read for URL /var/mobile/Media/DCIM/100APPLE/IMG_0202.M4V. Error: Operation not permitted}
I was stuck on the same problem today. Here is my solution. Hope this helps or guides you to the right path.
PHImageManager.default().requestExportSession(forVideo: video, options: nil, exportPreset: AVAssetExportPresetPassthrough) { (exportSession, nil) in
if let exportSession = exportSession {
exportSession.outputURL = destinationURLForFile
exportSession.outputFileType = AVFileType.m4v
exportSession.exportAsynchronously() {
// Load the share sheet using destinationURLForFile
}
}
}
What this does is export the video to the provided location destinationURLForFile (i used the Documents directory. Make sure you delete the file if its already there otherwise the export MAY not work cause it may not override the file).
You can set the type based on available types. I needed m4v.
Then, export async and just call the share sheet or whatever sharing mechanism you have.
I've run into an issue where I can't seem to figure out a good way of grabbing the UTI or MIME type from a video asset after fetching it from the photo library.
let assets = PHAsset.fetchAssets(in: someCollection, options: videosOnly)
(iterating over each asset in assets...)
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { (AVAsset: avAsset, AVAudioMix: avAudioMix, info) in
(I'm trying to find the UTI/MIME type here...)
}
One iffy solution I found was to grab the pathExtension of the file by casting the avAsset as a AVURLAsset:
guard let avURLAsset = avAsset as? AVURLAsset else { return }
let videoExt = avURLAsset.url.pathExtension
This seems to get the corresponding filetype ('m4v', 'mov', 'mp4') in basic test cases, but I'm worried that this is not a robust enough solution. I saw another post (Finding image type from NSData or UIImage) that details grabbing the image type by looking at the bytes of NSData, but did not touch on video files.
I have also tried the solution suggested at How to get MIME type for image or video in iOS 8 using PHAsset?:
let requestContentEditingOptions = PHContentEditingInputRequestOptions()
asset.requestContentEditingInput(with: requestContentEditingOptions) { (contentEditingInput, contentEditingInfo) in
guard let videoUTI = contentEditingInput?.uniformTypeIdentifier else { completion(nil); return }
}
Unfortunately, the uniformTypeIdentifier property came up as nil when I attempted this. If anyone sees a potential problem with my implementation of this, I'd love to hear it.
Has anyone else run into this before? Would love to hear any ideas or if anyone things the pathExtension is a reasonable option for a production app.
I am developing a share extension for photos for my iOS app. Inside the extension, I am able to successfully retrieve the UIImage object from the NSItemProvider.
However, I would like to be able to share the image with my container app, without having to store the entire image data inside my shared user defaults. Is there a way to get the PHAsset of the image that the user has chosen in the share extension (if they have picked from their device)?
The documentation on the photos framework (https://developer.apple.com/library/ios/documentation/Photos/Reference/Photos_Framework/) has a line that says "This architecture makes it easy, safe, and efficient to work with the same assets from multiple threads or multiple apps and app extensions."
That line makes me think there is a way to share the same PHAsset between extension and container app, but I have yet to figure out any way to do that? Is there a way to do that?
This only works if the NSItemProvider gives you a URL with the format:
file:///var/mobile/Media/DCIM/100APPLE/IMG_0007.PNG
which is not always true for all your assets, but if it returns a URL as:
file:///var/mobile/Media/PhotoData/OutgoingTemp/2AB79E02-C977-4B4A-AFEE-60BC1641A67F.JPG
then PHAsset will never find your asset. Further more, the latter is a copy of your file, so if you happen to have a very large image/video, iOS will duplicate it in that OutgoingTemp directory. Nowhere in the documentation says when it's going to be deleted, hopefully soon enough.
I think this is a big gap Apple has left between Sharing Extensions and PHPhotoLibrary framework. Apple should've be creating an API to close it, and soon.
You can get PHAsset if image is shared from Photos app. The item provider will give you a URL that contains the image's filename, you use this to match PHAsset.
/// Assets that handle through handleImageItem:completionHandler:
private var handledAssets = [PHAsset]()
/// Key is the matched asset's original file name without suffix. E.g. IMG_193
private lazy var imageAssetDictionary: [String : PHAsset] = {
let options = PHFetchOptions()
options.includeHiddenAssets = true
let fetchResult = PHAsset.fetchAssetsWithOptions(options)
var assetDictionary = [String : PHAsset]()
for i in 0 ..< fetchResult.count {
let asset = fetchResult[i] as! PHAsset
let fileName = asset.valueForKey("filename") as! String
let fileNameWithoutSuffix = fileName.componentsSeparatedByString(".").first!
assetDictionary[fileNameWithoutSuffix] = asset
}
return assetDictionary
}()
...
provider.loadItemForTypeIdentifier(imageIdentifier, options: nil) { imageItem, _ in
if let image = imageItem as? UIImage {
// handle UIImage
} else if let data = imageItem as? NSData {
// handle NSData
} else if let url = imageItem as? NSURL {
// Prefix check: image is shared from Photos app
if let imageFilePath = imageURL.path where imageFilePath.hasPrefix("/var/mobile/Media/") {
for component in imageFilePath.componentsSeparatedByString("/") where component.containsString("IMG_") {
// photo: /var/mobile/Media/DCIM/101APPLE/IMG_1320.PNG
// edited photo: /var/mobile/Media/PhotoData/Mutations/DCIM/101APPLE/IMG_1309/Adjustments/FullSizeRender.jpg
// cut file's suffix if have, get file name like IMG_1309.
let fileName = component.componentsSeparatedByString(".").first!
if let asset = imageAssetDictionary[fileName] {
handledAssets.append(asset)
imageCreationDate = asset.creationDate
}
break
}
}
}
I am trying to download images but it is crucial that the images I download are in a specific order. I am using the following code to download the image:
func downloadImage(url: NSURL, completionHandler: (response: UIImage) -> ()){
print("Started downloading \"\(url.URLByDeletingPathExtension!.lastPathComponent!)\".")
manager.getDataFromUrl(url) { (data, response, error) in
dispatch_async(dispatch_get_main_queue()) { () -> Void in
guard let data = data where error == nil else { return }
print("Finished downloading \"\(url.URLByDeletingPathExtension!.lastPathComponent!)\".")
completionHandler(response: UIImage(data: data)!)
}
}
}
and I am using this code to call downloadImage
self.downloadImage(NSURL(string: self.url)!, completionHandler: { response in
dispatch_async(dispatch_get_main_queue()) {
self.images.append(response)
}
})
The problem is that the images start downloading in the correct order however the response isn't (this is obviously because the size of the images are different and one comes in faster). So what is happening is that all images start downloading, and whichever comes first appends to the images : [UIImage] array. How can I make it so that the images in the images array is in order?
I've also tried to remove the main block when calling downloadImage function
self.downloadImage(NSURL(string: self.url)!, completionHandler: { response in
self.images.append(response)
})
You cannot control the download order in the sense that all the requests to the same server will be pipelined no matter what the order you create the URL objects in. Also, some URLs may be cached while others may need to go to the remote server. What you need to do is maintain a mutable array or dictionary that contains the url to actual data mapping, then wait until all the urls have been completely downloaded and then iterate in a known order.
The simplest method is that you can save every image in Dictionary with their url after downloading. Like var imageData = [String: NSData](). Later you can sort it or use it by keys(url).
Has anyone figured out how to extract the video portion from a Live Photo? I'm working on an app to convert Live Photos into a GIF, and the first step is to get the video file from the Live Photo. It seems like it should be possible, because if you plug in your phone to a Mac you can see the separate image and video files. I've kinda run into a brick wall in the extraction process, and I've tried many ways to do it and they all fail.
The first thing I did was obtain a PHAsset for what I think is the video part of the Live Photo, by doing the following:
if let livePhoto = info["UIImagePickerControllerLivePhoto"] as? PHLivePhoto {
let assetResources = PHAssetResource.assetResourcesForLivePhoto(livePhoto)
for assetRes in assetResources {
if (assetRes.type == .PairedVideo) {
let assets = PHAsset.fetchAssetsWithLocalIdentifiers([assetRes.assetLocalIdentifier], options: nil)
if let asset = assets.firstObject as? PHAsset {
To convert the PHAsset to an AVAsset I've tried:
asset.requestContentEditingInputWithOptions(nil, completionHandler: { (contentEditingInput, info) -> Void in
if let url = contentEditingInput?.fullSizeImageURL {
let movieUrl = url.absoluteString + ".mov"
let avAsset = AVURLAsset(URL: NSURL(fileURLWithPath: movieUrl), options: nil)
debugPrint(avAsset)
debugPrint(avAsset.duration.value)
}
})
I don't think this one works because the debug print with the duration.value gives 0.
I've also tried without the ".mov" addition and it still doesn't work.
I also tried:
PHImageManager.defaultManager().requestAVAssetForVideo(asset, options: nil, resultHandler: { (avAsset, audioMix, info) -> Void in
debugPrint(avAsset)
})
And the debugPrint(avAsset) prints nil so it doesn't work.
I'm kind of afraid they might have made it impossible to do, it seems like I'm going in circles since it seems like the PHAsset I got is still a Live Photo and not actually a video.
Use the PHAssetResourceManager to get the video file from the PHAssetResource.
PHAssetResourceManager.defaultManager().writeDataForAssetResource(assetRes,
toFile: fileURL, options: nil, completionHandler:
{
// Video file has been written to path specified via fileURL
}
NOTE: The Live Photo specific APIs were introduced in iOS 9.1
// suppose you have PHAsset instance (you can get it via [PHAsset fetchAssetsWithOptions:...])
PHAssetResource *videoResource = nil;
NSArray *resourcesArray = [PHAssetResource assetResourcesForAsset:asset];
const NSInteger livePhotoAssetResourcesCount = 2;
const NSInteger videoPartIndex = 1;
if (resourcesArray.count == livePhotoAssetResourcesCount) {
videoResource = resourcesArray[videoPartIndex];
}
if (videoResource) {
NSString * const fileURLKey = #"_fileURL";
NSURL *videoURL = [videoResource valueForKey:fileURLKey];
// load video url using AVKit or AVFoundation
}
I accidentally did. I have an ios app called Goodreader (available in the appstore) which features a windows-like file manager. When importing a live photo, it will save it as a folder ending in .pvt containing the jpg and mov files in it. There is only one caveat: you need to open the live photo from within the messages app after you've sent it to yourself or somebody else to see the "import to goodreader" option, not from the photos app.