Get File Extension from a PHAsset in iOS 8? - ios

Is there any way to distinguish a PHAsset's file extension? My app needs to know if the photo is a .GIF or not. I'm not seeing any way to do this.

A better approach would be to use uniformTypeIdentifier. Files can be named anything and may or may not contain a file extension.
[asset valueForKey:#"uniformTypeIdentifier"];

Using valueForKey: you can get the PHAsset file name from that you extract file extension using "pathExtension"
For Example:
PHAsset *selectedAsset = ...
NSString *fileName =[selectedAsset valueForKey:#"filename"];
NSString * fileExtension = [fileName pathExtension];

File names are an implementation detail, and may or may not tell you what the content of a file really is.
When you call requestImageDataForAsset, the dataUTI parameter that Photos passes to your completion handler tells you the Uniform Type Identifier for the image data.

PHAssetResource.uniformTypeIdentifier seems to do the job.
PHAssets can be fetched by using the class function PHAssetResource. assetResources(for:).
An image does NOT mean only there will be only one item in the list, so check out the documentation.

Swift 5 Elegant Solution
extension PHAsset {
func getFileName() -> String? {
return self.value(forKey: "filename") as? String
}
func getExtension() -> String? {
guard let fileName = self.getFileName() else {
return nil
}
return URL(fileURLWithPath: fileName).pathExtension
}
}

In Swift, Use:
URL(fileURLWithPath: (asset.value(forKey: "filename") as! String)).pathExtension

Related

How do I grab the UTI or MIME Type from a video asset fetched using PHAsset.fetchAsset?

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.

Create MSSticker from UIImage

How do I create an MSSticker from a UIImage? The only initializer for an MSSticker is from a local URL, which makes me believe that I first must write the UIImage to a file. If that's the case, could someone explain how to do this because I have had trouble doing so. I was successfully able to write to a file using NSData.writeToFile(), but I could not figure out what URL to pass to the MSSticker initializer.
There's a solution readily available in this other thread: iOS 10 Message Extension app: How can I get the MSSticker created by this function?
The idea is the same you mention in your question: save the images to disk, and then load the MSSticker instance from them.
You can create MSSticker using UIImage , first you need to put resources in bundle not is assets.xcassets folder. after that just use this simple method to create sticker object.
let imagePath = Bundle.main.path(forResource: imgName, ofType: ".png")
let pathurl = URL(fileURLWithPath: imagePath!)
do {
try cell.stickerview.sticker = MSSticker(contentsOfFileURL: pathurl, localizedDescription: "anything that you want")
}
catch {
fatalError("Failed to create sticker: \(error)")
}

Get PHAsset from iOS Share Extension

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
}
}
}

Copy GIF to UIPasteboard

I'm trying to copy a GIF image to the UIPasteboard in swift, at the moment it only copies the static version of the image and seems to convert it to PNG looking at the file extension when I upload it somewhere.
Wondered if anyone had any idea how to achieve this? All other soltions I've found only seem to work when getting NSData from a URL rather than from an image in the bundle
For anyone who ever encounters this problem I managed to find a solution
let url: NSURL = NSBundle.mainBundle().URLForResource("\(self.imageNames[indexPath.row])", withExtension: ".gif")!
let data: NSData = NSData(contentsOfURL: url)!
UIPasteboard.generalPasteboard().setData(data, forPasteboardType: "com.compuserve.gif")
As it turns out you do need to use a URL and extract the NSData of the GIF from that URL.
Here I am getting the URL of the GIF that is in my bundle, searching for it using the name and extension of the image. I am then setting the data in the pasteboard and bingo we have an animated GIF when pasting the result from the pasteboard
It doesn't look like the image property on the pasteboard supports the GIF type.
The associated array of representation types is UIPasteboardTypeListImage, which includes types kUTTypePNG and kUTTypeJPEG.
You could probably do this using the NSData from the GIF though:
import MobileCoreServices
// ...
var image = UIImage(...)
let data = NSData(bytes: &image, length: sizeof(UIImage))
UIPasteboard.generalPasteboard().setData(data, forPasteboardType: kUTTypeGIF as String)) // com.compuserve.gif

Swift Share extension, ALAsset is nil

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.

Resources