I'm trying to create a sharing extension using the new iOS 8 app extensions. I tried to get the current URL of a Safari site to show it in a UILabel. Simple enough.
I was working trough the official extension guide from apple here https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/Share.html#//apple_ref/doc/uid/TP40014214-CH12-SW1 but some things are not working as expected. I know it is only in beta but maybe I'm just doing something wrong.
Here is my code to get the URL from safari inside the extensions ViewController:
-(void)viewDidAppear:(BOOL)animated{
NSExtensionContext *myExtensionContext = [self extensionContext];
NSArray *inputItems = [myExtensionContext inputItems];
NSMutableString* mutableString = [[NSMutableString alloc]init];
for(NSExtensionItem* item in inputItems){
NSMutableString* temp = [NSMutableString stringWithFormat:#"%#, %#, %lu,
%lu - ",item.attributedTitle,[item.attributedContentText string],
(unsigned long)[item.userInfo count],[item.attachments count]];
for(NSString* key in [item.userInfo allKeys]){
NSArray* array = [item.userInfo objectForKey:#"NSExtensionItemAttachmentsKey"];
[temp appendString:[NSString stringWithFormat:#" in array:%lu#",[array count]]];
}
[mutableString appendString:temp];
}
self.myLabel.text = mutableString;
}
And this is the content of my Info.plist file of my Extension:
<dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>200</integer>
</dict>
</dict>
When I visit apples iPod support page in Safari and try to share it to my extension, I get following values but no URL:
item.attributedTitle = (null)
item.attributedContentText = "iPod - Apple Support"
item.userInfo.count = 2 (two keys: NSExtensionAttributedContentTextKey and
NSExtensionItemAttachmentsKey)
item.attachments.count = 0
The arrays inside the objects of the dictionary are always empty.
When I share the apple site with the system mail app the URL is posted to the message. So why is there no URL in my extension?
Below is how you can get the url. Notice the type identifier is kUTTypeURL and the block argument is NSURL. Also, the plist needs to be correct like mine also. The documentation was lacking and got help from number4 on the Apple dev forums. (you'll need to be registered and logged in to see it).
Code:
NSExtensionItem *item = self.extensionContext.inputItems.firstObject;
NSItemProvider *itemProvider = item.attachments.firstObject;
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeURL]) {
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeURL options:nil completionHandler:^(NSURL *url, NSError *error) {
self.urlString = url.absoluteString;
}];
}
Info.plist
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>
<key>NSExtensionPointName</key>
<string>com.apple.share-services</string>
<key>NSExtensionPointVersion</key>
<string>1.0</string>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
</dict>
I've solved it for myself. I was trying with Sharing Image.
- (void)didSelectPost {
// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
// Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
// Verify that we have a valid NSExtensionItem
NSExtensionItem *imageItem = [self.extensionContext.inputItems firstObject];
if(!imageItem){
return;
}
// Verify that we have a valid NSItemProvider
NSItemProvider *imageItemProvider = [[imageItem attachments] firstObject];
if(!imageItemProvider){
return;
}
// Look for an image inside the NSItemProvider
if([imageItemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]){
[imageItemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:nil completionHandler:^(UIImage *image, NSError *error) {
if(image){
NSLog(#"image %#", image);
// do your stuff here...
}
}];
}
// this line should not be here. Cos it's called before the block finishes.
// and this is why the console log or any other task won't work inside the block
[self.extensionContext completeRequestReturningItems:nil completionHandler:nil];
}
So what I did is just moved the [self.extensionContext completeRequestReturningItems:nil completionHandler:nil]; inside the block at the end of other tasks. The final working version look like this (Xcode 6 beta 5 on Mavericks OS X 10.9.4):
- (void)didSelectPost {
// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
// Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
// Verify that we have a valid NSExtensionItem
NSExtensionItem *imageItem = [self.extensionContext.inputItems firstObject];
if(!imageItem){
return;
}
// Verify that we have a valid NSItemProvider
NSItemProvider *imageItemProvider = [[imageItem attachments] firstObject];
if(!imageItemProvider){
return;
}
// Look for an image inside the NSItemProvider
if([imageItemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]){
[imageItemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:nil completionHandler:^(UIImage *image, NSError *error) {
if(image){
NSLog(#"image %#", image);
// do your stuff here...
// complete and return
[self.extensionContext completeRequestReturningItems:nil completionHandler:nil];
}
}];
}
// this line should not be here. Cos it's called before the block finishes.
// and this is why the console log or any other task won't work inside the block
// [self.extensionContext completeRequestReturningItems:nil completionHandler:nil];
}
I hope it'll work for URL sharing as well.
Your extension view controller should be adopting the NSExtensionRequestHandling protocol. One of this protocol's methods is:
- (void)beginRequestWithExtensionContext:(NSExtensionContext *)context
You should be waiting for this to be called before you attempt to get the NSExtensionContext. It even provides the context in the method as the context parameter.
This was outlined in this document.
All of those previous answers are really good but I just came accross this issue in Swift and felt it was a little tidious to extract the URL from a given NSExtensionContext especially in the CFString to String conversion process and the fact that the completionHandler in loadItemForTypeIdentifier is not executed in the main thread.
import MobileCoreServices
extension NSExtensionContext {
private var kTypeURL:String {
get {
return kUTTypeURL as NSString as String
}
}
func extractURL(completion: ((url:NSURL?) -> Void)?) -> Void {
var processed:Bool = false
for item in self.inputItems ?? [] {
if let item = item as? NSExtensionItem,
let attachments = item.attachments,
let provider = attachments.first as? NSItemProvider
where provider.hasItemConformingToTypeIdentifier(kTypeURL) == true {
provider.loadItemForTypeIdentifier(kTypeURL, options: nil, completionHandler: { (output, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
processed = true
if let url = output as? NSURL {
completion?(url: url)
}
else {
completion?(url: nil)
}
})
})
}
}
// make sure the completion block is called even if no url could be extracted
if (processed == false) {
completion?(url: nil)
}
}
}
That way you can now simply use it like this in your UIViewController subclass:
self.extensionContext?.extractURL({ (url) -> Void in
self.urlLabel.text = url?.absoluteString
println(url?.absoluteString)
})
The other answers are all complicated and incomplete. They only work in Safari and do not work in Google Chrome. This works both in Google Chrome and Safari:
override func viewDidLoad() {
super.viewDidLoad()
for item in extensionContext!.inputItems {
if let attachments = item.attachments {
for itemProvider in attachments! {
itemProvider.loadItemForTypeIdentifier("public.url", options: nil, completionHandler: { (object, error) -> Void in
if object != nil {
if let url = object as? NSURL {
print(url.absoluteString) //This is your URL
}
}
})
}
}
}
}
You need to be looking for an attachment of type kUTTypePropertyList. Do something like this with the first attachment of the first extension item in your extension:
NSExtensionItem *extensionItem = self.extensionContext.extensionItems.firstObject;
NSItemProvider *itemProvider = self.extensionItem.attachments.firstObject;
[itemProvider loadItemForTypeIdentifier:kUTTypePropertyList options:nil
completionHandler:^(NSDictionary *item, NSError *error) {
// Unpack items from "item" in here
}];
You'll also need to setup some JavaScript to pick up all of the data you need from the DOM. See the second extensions session from WWDC 14 for more details.
let itemProvider = item.attachments?.first as! NSItemProvider
itemProvider.loadItemForTypeIdentifier("public.url", options: nil) { (object, error) -> Void in
println(object)
}
so println :
http://detail.m.tmall.com/item.htm?id=38131345289&spm=a2147.7632989.mainList.5
Related
I just added MP4/GIF attachments to my push notifications in my iOS app. Everything works fine with respect to playback. The issue I am facing is when MP4 videos are sent, the small thumbnail that is sent in the push looks transparent. However, when I expand it, it looks perfect and I can play it well too inside the push. When I send the same video converted to GIF the thumbnail also looks perfect.
Here is an example:
The example above shows two different apps, just to show how MP4 and GIF thumbnails show up for the same event. If I were to send GIF to the app on the top, the output of the thumbnail looks exactly like the Pushover app thumbnail.
And here is what happens when I slide and view the thumbnail (transparent one). This particular expanded thumbnail is for a different event (I lost that old event). But the point I wanted to make is the expanded view looks perfect. And plays perfectly too.
So in conclusion, in IOS, when I send MP4 files as attachments the small thumbnail looks transparent, but plays back well. expanded thumbnail looks perfect.
This is my client code:
//
// NotificationService.m
// NotificationService
//
//
//
//
// Credit https://github.com/Leanplum/Leanplum-iOS-Samples/blob/master/iOS_basicSetup/basicSetup/richPushExtension/NotificationService.m
#import "NotificationService.h"
#interface NotificationService ()
#property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
#property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
#end
#implementation NotificationService
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
NSDictionary *userInfo = request.content.userInfo;
// If there is no image in the payload than
// the code will still show the push notification.
if (userInfo == nil || userInfo[#"image_url_jpg"] == nil) {
NSLog(#"zmNinja Notification: Did not get a payload or image");
[self contentComplete];
return;
}
NSString *mediaUrl = userInfo[#"image_url_jpg"];
// if (mediaType == nil) {
// NSLog(#"zmNinja Notification: No media type specified, assuming .jpg");
// mediaType = #".jpg";
// }
// load the attachment
[self loadAttachmentForUrlString:mediaUrl
completionHandler:^(UNNotificationAttachment *attachment) {
if (attachment) {
self.bestAttemptContent.attachments = [NSArray arrayWithObject:attachment];
}
[self contentComplete];
}];
}
- (NSString*)determineType:(NSString *) fileType {
// Determines the file type of the attachment to append to NSURL.
//return #".gif";
// Determines the file type of the attachment to append to NSURL.
NSLog (#"zmNinja Notification: determineType got filetype=%#",fileType);
if ([fileType isEqualToString:#"image/jpeg"]){
NSLog (#"zmNinja Notification: returning JPG");
return #".jpg";
}
if ([fileType isEqualToString:#"video/mp4"]){
NSLog (#"zmNinja Notification: returning MP4");
return #".mp4";
}
if ([fileType isEqualToString:#"image/gif"]) {
NSLog (#"zmNinja Notification: returning GIF");
return #".gif";
}
if ([fileType isEqualToString:#"image/png"]) {
NSLog (#"zmNinja Notification: returning PNG");
return #".png";
}
NSLog (#"zmNinja Notification: unrecognized filetype, returning JPG");
return #".jpg";
}
- (void)loadAttachmentForUrlString:(NSString *)urlString
completionHandler:(void(^)(UNNotificationAttachment *))completionHandler {
__block UNNotificationAttachment *attachment = nil;
NSURL *attachmentURL = [NSURL URLWithString:urlString];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
[[session downloadTaskWithURL:attachmentURL
completionHandler:^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) {
if (error != nil) {
NSLog(#"unable to add attachment: %#", error.localizedDescription);
} else {
NSString *fileType = [self determineType: [response MIMEType]];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *localURL = [NSURL fileURLWithPath:[temporaryFileLocation.path stringByAppendingString:fileType]];
[fileManager moveItemAtURL:temporaryFileLocation toURL:localURL error:&error];
NSError *attachmentError = nil;
attachment = [UNNotificationAttachment attachmentWithIdentifier:#"" URL:localURL options:nil error:&attachmentError];
if (attachmentError) {
NSLog(#"unable to add attchment: %#", attachmentError.localizedDescription);
}
}
completionHandler(attachment);
}] resume];
}
- (void)contentComplete {
self.contentHandler(self.bestAttemptContent);
}
- (void)serviceExtensionTimeWillExpire {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
NSLog (#"zmNinja Notification: Time about to expire, handing off to best attempt");
self.contentHandler(self.bestAttemptContent);
}
#end
The server side uses FCM legacy APIs:
my $ios_message = {
to => $obj->{token},
notification => {
title => $title,
body => $body,
sound => "default",
badge => $badge,
},
data => {
myMessageId => $notId,
mid => $mid,
eid => $eid,
summaryText => $eid
}
};
$ios_message->{data}->{image_url_jpg} = $pic; # $pic is a URL for the mp4
# image_url_jpg is just a field name. It was originally meant for static images
# haven't changed it yet, as you see in client code above, it uses that field.
$json = encode_json($ios_message);
my $req = HTTP::Request->new( 'POST', $uri );
$req->header(
'Content-Type' => 'application/json',
'Authorization' => $key
);
$req->content($json);
my $lwp = LWP::UserAgent->new(%ssl_push_opts);
my $res = $lwp->request($req);
Finally, if you want to take a look at a sample MP4 to rule out any format issue, here is one that I've uploaded to google drive (link). I've extracted frame information with ffshow and it doesn't look out of place to me (plus it plays perfectly).
Can someone help me understand why the initial small thumbnail looks messed up in iOS? (If it helps, I am on iOS 13.x)
Thanks.
Had the same problem. Solved it by creating a notification content extension and sending a thumbnail image URL along with the video URL.
In the service extension, I add both the video and the thumbnail as attachments, with the thumbnail as the first element, which iOS will show in the notification preview:
// mediaAttachment and thumbnailAttachment are UNNotificationAttachments
// that have just been downloaded
if let mediaAttachment = mediaAttachment {
mutableContent.attachments = [mediaAttachment]
}
if let thumbnailAttachment = thumbnailAttachment {
mutableContent.attachments.insert(thumbnailAttachment, at: 0)
}
contentHandler(mutableContent)
The expanded notification UI is handled by the content extension which replaces the default UI, and there I ignore the thumbnail image and just show the video, which will be the last attachment.
I used the Solution form iOS Share Extension issue when sharing images from Photo library to get Images from the Photo App. This works great in the Simulator, but on the Device I get an error that I can't Access the NSURL provided by the itemProvider:
2018-02-18 12:54:09.448148+0100 MyApp[6281:1653186] [default] [ERROR] Failed to determine whether URL /var/mobile/Media/PhotoData/OutgoingTemp/554581B2-950C-4CFD-AE67-A2342EDEA04D/IMG_2784.JPG (s) is managed by a file provider
Caused by the Statment:
[itemProvider loadItemForTypeIdentifier:itemProvider.registeredTypeIdentifiers.firstObject options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
}
Searching PHAsset for the Item Name is not a good solution as the user have to grand access to the photo library again.
In didSelectPost you must not dismiss the ShareExtension ViewController until you have processed all the items in the inputItems array. The ViewController is dismissed using [super didSelectPost] or by calling the completion method of the extension context.
Here is my solution in code:
- (void)didSelectPost {
__block NSInteger itemCount = ((NSExtensionItem*)self.extensionContext.inputItems[0]).attachments.count;
__block NSInteger processedCount = 0;
for (NSItemProvider* itemProvider in ((NSExtensionItem*)self.extensionContext.inputItems[0]).attachments ) {
if([itemProvider hasItemConformingToTypeIdentifier:#"public.jpeg"]) {
NSLog(#"itemprovider = %#", itemProvider);
[itemProvider loadItemForTypeIdentifier:#"public.jpeg" options:nil completionHandler: ^(id<NSSecureCoding> item, NSError *error) {
NSData *imgData;
if([(NSObject*)item isKindOfClass:[NSURL class]]) {
imgData = [NSData dataWithContentsOfURL:(NSURL*)item];
}
if([(NSObject*)item isKindOfClass:[UIImage class]]) {
imgData = UIImagePNGRepresentation((UIImage*)item);
}
NSDictionary *dict = #{
#"imgData" : imgData,
#"name" : self.contentText
};
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.share.extension1"];
[defaults setObject:dict forKey:#"img"];
[defaults synchronize];
processedCount += 1;
if (processedCount == itemCount)
[super didSelectPost];
}];
}
}
The loadItemForTypeIdentifier or in Swift loadItem method is asynchronous so dismissing the UI must be called as the last thing inside its completionHandler.
For example I have:
override func didSelectPost() {
// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
if let item = self.extensionContext?.inputItems[0] as? NSExtensionItem, let attachments = item.attachments {
for provider in attachments {
if provider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
provider.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil, completionHandler: {item, error in
// do all you need to do here, i.e.
if let tmpURL = item as? URL {
// etc. etc.
}
// and, at the end, inside the completionHandler, call
// Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
})
}
}
}
}
I you dismiss the UI via:
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
or via:
super.didSelectPost()
outside of the completionHandler after the async method loadItem you will get all kind of permission errors, further more this errors could be random, sometimes happen and sometimes don't, this is because sometimes your async call to loadItem gets the chance to terminate before the UI is dismissed and sometimes it doesn't.
Just leaving this here, hoping it helps someone. This issue costed me few hours.
I am creating an app with Share Extension. The sharing works like a charm on Chrome, Safari, and many more, but EXCEPT for PocketApp. If I try to share a link from PocketApp to my ShareExtension App, the hasItemConformingToTypeIdentifier(kUTTypeURL) function returns true. But during the loadItemForTypeIdentifier, the completionHandler ALWAYS RETURNS an ERROR.
Any thought about this?
- (void)didSelectPost{
inputItem = self.extensionContext.inputItems.firstObject;
for ( NSItemProvider * itemProvider in inputItem.attachments )
{
if ([itemProvider hasItemConformingToTypeIdentifier:(__bridge NSString *)kUTTypeURL])
{
NSLog(#"Attachment is a URL");
[itemProvider loadItemForTypeIdentifier:(__bridge NSString *)kUTTypeURL options:nil completionHandler:^(NSURL *url, NSError *error)
{
if (error)
{
NSLog(#"Error occured");
}
else
{
NSLog(#"Success occured");
}
}
}
}
I am sure this is trivial once someone kindly point me in the right direction so my apology for asking a silly question. However I have been searching for days I can't figure out what I am doing wrong.
Scenario: create a simple share extension that receives an image file
Problem: when I access the attachements, the handler is never called albeit I can see the "public.jpg" in the itemProvider but I can't see where the data would be?
What I have done:
1) defined NSExtensionActivationSupportsImageWithMaxCount = 1 as my only activation rule
2) added CoreMedia framework to the extension
3) added the same group to both app and app extension
4) made sure both have the group (1) in the entitlement
5) made sure both are using a certificate/app id with that group enabled
6) clean and rebuild several times to no avail.
The code:
- (void)didSelectPost {
/
for (NSExtensionItem *item in self.extensionContext.inputItems) {
for (NSItemProvider *itemProvider in item.attachments) {
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {
I can hit this breakpoint --> [itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:nil completionHandler:^(UIImage *image, NSError *error) {
but not this one --> photo = image;
}];
break;
}
}
}
.... and so on and so forth
You haven't posted your complete code but I suspect that you are calling completeRequestReturningItems:completionHandler: at the wrong location:
WRONG:
- (void)didSelectPost {
NSExtensionItem *item = self.extensionContext.inputItems.firstObject;
NSItemProvider *itemProvider = item.attachments.firstObject;
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage
options:nil
completionHandler:^(NSURL *url, NSError *error) {
// send the image
}];
}
// ↓ this is the wrong location ↓
[self.extensionContext completeRequestReturningItems:#[] completionHandler:nil];
}
The problem is that calling completeRequestReturningItems:completionHandler: immediately dismisses the ShareViewController and deallocates it. So the NSItemProvider that contains the image is also destroyed before it can access the image (because it loads its items asynchronously). In other words: the completion handler in which you send the image to your server is never called, because the whole shareViewController has already been deallocated.
To fix that problem you have to move the call to completeRequestReturningItems:completionHandler: to the end of the completion handler AFTER you send the image.
CORRECT:
- (void)didSelectPost {
NSExtensionItem *item = self.extensionContext.inputItems.firstObject;
NSItemProvider *itemProvider = item.attachments.firstObject;
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage
options:nil
completionHandler:^(NSURL *url, NSError *error) {
// send the image
[self.extensionContext completeRequestReturningItems:#[]
completionHandler:nil];
}];
}
}
This code worked fine in iOS 7 but in iOS 8.1 all assets located in the "My Photo Stream" album are nil from within the result block. (The failureBlock is not called.) Regular albums and shared albums work just fine.
I tried the accepted answer from: Error trying to assigning __block ALAsset from inside assetForURL:resultBlock:
That is, I'm holding a reference to an ALAssetsLibrary object, listening for the ALAssetsLibraryChangedNotification event (which doesn't happen btw, but oh well.) I made sure my app has permission to access photos, I'm on wi-fi, I see the photos' thumbnails just fine in my tableView. It's just when I try to load them with assetForURL: they're always nil.
// example URL: assets-library://asset/asset.JPG?id=1ECB69B9-DC7A-45A7-B135-F43317D3412C&ext=JPG
[self.library assetForURL:[NSURL URLWithString:url] resultBlock:^(ALAsset *asset) {
NSLog(#"Asset: %#", asset); // nil :(
} failureBlock:^(NSError *error) {
NSLog(#"Failure, wahhh!");
}];
Is anyone else seeing this issue?
I had the same problem. Switching to Photos framework is not an option for me at this moment, but fortunately I have found a workaround. You may find it a big ugly and I suspect it may work slow when Photo Stream contains a lot of photos, but it is better than nothing.
The idea is to enumerate all items in the Photo Stream asset group and compare the necessary URL with the URL of each item. Fortunately, it still works.
I have a method like this (library is ALAssetsLibrary property of the same class, you may need to initialise it inside this code):
- (void)loadItem:(NSURL *)url withSuccessBlock:(void (^)(void))successBlock andFailureBlock:(void (^)(void))failureBlock {
[library assetForURL:url
resultBlock:^(ALAsset *asset)
{
if (asset){
//////////////////////////////////////////////////////
// SUCCESS POINT #1 - asset is what we are looking for
//////////////////////////////////////////////////////
successBlock();
}
else {
// On iOS 8.1 [library assetForUrl] Photo Streams always returns nil. Try to obtain it in an alternative way
[library enumerateGroupsWithTypes:ALAssetsGroupPhotoStream
usingBlock:^(ALAssetsGroup *group, BOOL *stop)
{
[group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if([result.defaultRepresentation.url isEqual:url])
{
///////////////////////////////////////////////////////
// SUCCESS POINT #2 - result is what we are looking for
///////////////////////////////////////////////////////
successBlock();
*stop = YES;
}
}];
}
failureBlock:^(NSError *error)
{
NSLog(#"Error: Cannot load asset from photo stream - %#", [error localizedDescription]);
failureBlock();
}];
}
}
failureBlock:^(NSError *error)
{
NSLog(#"Error: Cannot load asset - %#", [error localizedDescription]);
failureBlock();
}
];
}
Hope this helps.
I've made the observation that trying to retrieve an asset using assetForURL inside a writeImage toSavedPhotosAlbum success block for that same asset will yield an asset of nil (most of the time).
However, retrieving the asset with assetForURL some time after the writeImage success block has completed execution does yield the correct asset.
Waiting for 1 second did work, while waiting for only 300 ms did not. But this of course will be different for each and every device and situation.
This does not really answer the question in a satisfying way, but maybe it helps someone else figuring out the underlying problem.
From iOS 8.0 and later, Apple suggests to use Photos framework instead of the Assets Library framework.
Tested with iPad mini on iOS 8.1, this is how you should do it with the new Photos Framework:
NSURL *url = /* your asset url from the old ALAsset library prior to iOS 8 */
PHFetchResult<PHAsset *> *assets = [PHAsset fetchAssetsWithALAssetURLs:#[url]
options:nil];
assert(assets.count == 1);
PHAsset *asset = assets.firstObject;
[[PHImageManager defaultManager] requestImageForAsset:asset
targetSize:CGSizeMake(800, 800) // TODO: your target size
contentMode:PHImageContentModeDefault
options:nil
resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info)
{
// Do whatever you want to the result
}];
After not finding any answers for this anywhere, I created the following extension to PHAsset which works great as of iOS 8.2 although I assume it's theoretically slow. Even though one of the prior comments says that this is fixed on iOS8.2 beta, the bug was still present for me now that iOS8.2 is released.
import Photos
import UIKit
extension PHAsset {
class func fetchAssetWithALAssetURL (alURL: NSURL) -> PHAsset? {
let phPhotoLibrary = PHPhotoLibrary.sharedPhotoLibrary()
let assetManager = PHImageManager()
var phAsset : PHAsset?
let optionsForFetch = PHFetchOptions()
optionsForFetch.includeHiddenAssets = true
var fetchResult = PHAsset.fetchAssetsWithALAssetURLs([alURL], options: optionsForFetch)
if fetchResult?.count > 0 {
return fetchResult[0] as? PHAsset
} else {
var str = alURL.absoluteString!
let startOfString = advance(find(str, "=")!, 1)
let endOfString = advance(startOfString, 36)
let range = Range<String.Index>(start:startOfString, end:endOfString)
let localIDFragment = str.substringWithRange(range)
let fetchResultForPhotostream = PHAssetCollection.fetchAssetCollectionsWithType(PHAssetCollectionType.Album, subtype: PHAssetCollectionSubtype.AlbumMyPhotoStream, options: nil)
if fetchResultForPhotostream?.count > 0 {
let photostream = fetchResultForPhotostream![0] as PHAssetCollection
let fetchResultForPhotostreamAssets = PHAsset.fetchAssetsInAssetCollection(photostream, options: optionsForFetch)
if fetchResultForPhotostreamAssets?.count >= 0 {
var stop : Bool = false
for var i = 0; i < fetchResultForPhotostreamAssets.count && !stop; i++ {
let phAssetBeingCompared = fetchResultForPhotostreamAssets[i] as PHAsset
if phAssetBeingCompared.localIdentifier.rangeOfString(localIDFragment, options: nil, range: nil, locale: nil) != nil {
phAsset = phAssetBeingCompared
stop = true
}
}
return phAsset
}
}
return nil
}
}
}