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];
}];
}
}
Related
When I am sending a panorama image (made with default panorama mode of my iPhone) to my iOS Share extension, hasItemConformingToTypeIdentifier tells me that there is indeed an image sent, however loadItemForTypeIdentifier does not return anything.I don't have any completeRequestReturningItems on the whole flow which could prevent the loadItemForTypeIdentifier to proceed until the end. This works however fine with a normal image.
How come panorama images give issues ?
for (NSExtensionItem *item in self.extensionContext.inputItems)
{
for (NSItemProvider *itemProvider in item.attachments)
{
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage])
{
NSLog(#"Found item:%#",item); --> Is displayed
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:nil completionHandler:^(UIImage *image, NSError *error)
{
NSLog(#"Item delivered"); --> Is never displayed for panorama pictures
if(image) {
selectedImage=image;
mediaType=1;
NSLog(#"Image found !!");
imageFound = YES;
}
}];
break;
}
}
}
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've managed to successfully upload data using the AWS 2.0 SDK, however I'm having some trouble understanding the "re-wiring" of completion handlers that is meant to take place on resuming of the app.
What I believe I should be doing, is at comment 6, saving the upload task. Then if it reaches the completionHandler block, deleting it. However, if the app is terminated before this, I can look at my saved upload tasks in the block at comment 4. Any saved tasks would be "re-wired" to the completion handler.
For reference, imagine this code happening in an "Upload" class, and in a block, which is the "uploadSuccess()" code you see in the completionHandler.
For reference here is the blog that introduced the AWSS3TransferUtility, and here is its documentation.
Hopefully someone can guide me on this.
// 1. Get/Setup credentials
AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWSRegionUSEast1 identityPoolId:#"IdentityPoolId"];
AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1 credentialsProvider:credentialsProvider];
AWSServiceManager.defaultServiceManager.defaultServiceConfiguration = configuration;
// 2. Create AWSS3 Transfer blocks
AWSS3TransferUtilityUploadExpression *expression = [AWSS3TransferUtilityUploadExpression new];
expression.uploadProgress = ^(AWSS3TransferUtilityTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) {
dispatch_async(dispatch_get_main_queue(), ^{
// Do something e.g. Update a progress bar.
});
};
AWSS3TransferUtilityUploadCompletionHandlerBlock completionHandler = ^(AWSS3TransferUtilityUploadTask *task, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
// Do something e.g. Alert a user for transfer completion.
// On failed uploads, `error` contains the error object.
if (error) {
uploadSuccess(NO);
} else {
uploadSuccess(YES);
}
});
};
// 3. Create Transfer Utility
AWSS3TransferUtility *transferUtility = [AWSS3TransferUtility defaultS3TransferUtility];
// 4. Rewire Transfer Utility blocks
[transferUtility
enumerateToAssignBlocksForUploadTask:^(AWSS3TransferUtilityUploadTask *uploadTask, __autoreleasing AWSS3TransferUtilityUploadProgressBlock *uploadProgressBlockReference, __autoreleasing AWSS3TransferUtilityUploadCompletionHandlerBlock *completionHandlerReference) {
NSLog(#"Upload task identifier = %lu", (unsigned long)uploadTask.taskIdentifier);
// Use `uploadTask.taskIdentifier` to determine what blocks to assign.
//*uploadProgressBlockReference = // Reassign your progress feedback block.
*completionHandlerReference = completionHandler;// Reassign your completion handler.
}
downloadTask:^(AWSS3TransferUtilityDownloadTask *downloadTask, __autoreleasing AWSS3TransferUtilityDownloadProgressBlock *downloadProgressBlockReference, __autoreleasing AWSS3TransferUtilityDownloadCompletionHandlerBlock *completionHandlerReference) {
}];
// 5. Upload data using Transfer Utility
[[transferUtility uploadData:myNSDataObject
bucket:#"bucketName"
key:#"keyName"
contentType:#"text/plain"
expression:expression
completionHander:completionHandler] continueWithBlock:^id(AWSTask *task) {
if (task.error) {
NSLog(#"Error: %#", task.error);
}
if (task.exception) {
NSLog(#"Exception: %#", task.exception);
}
if (task.result) {
AWSS3TransferUtilityUploadTask *uploadTask = task.result;
// 6. Should i be saving uploadTasks here?
}
return nil;
}];
I'm getting stuck with an error when using my Watchkit Application. When I launch it, I ask the containing iOS app to get some data from network. The problem is that I get an error saying the containing app never calls 'reply()' :o But looking at my code, it should call it.
I tried to debug every step from openParentApplication to the 'reply()' call, and it seems to work well =X
Here is my code in the Watchkit extension
- (void)initDiaporamasWithSuccess:(void (^)())success andFailure:(void (^)(NSError*))failure {
NSLog(#"Ask to load diapos");
__weak typeof(self) weakSelf = self;
[WKInterfaceController openParentApplication:#{#"watchKit": #"watchKit.initDiapos"} reply:^(NSDictionary *replyInfo, NSError *error) {
if (error) {
NSLog(#"%#", error);
if (failure) {
failure(error);
}
return;
}
NSLog(#"got items : %#", replyInfo[#"diapos"]);
weakSelf.diaporamas = replyInfo[#"diapos"];
[weakSelf setDiaporama:replyInfo[#"firstDiapo"] AtIndex:0];
if (success) {
success();
}
}];
}
The result should be an NSDictionary containing an NSArray with some diaporamas basic informations, and an object (Diapo) containing the full informations of the first diaporama (e.g. self.diaporamas[0])
And here is the code in the containing app's AppDelegate :
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply {
// Maybe we could handle multiple watchKit extension calls that way ?
// Something like a key-value 'protocol' to run the right block of code
NSString *watchKitCall = userInfo[#"watchKit"];
NSLog(#"watchKit handled");
if ([watchKitCall isEqualToString:#"watchKit.initDiapos"]) {
[AppDelegate watchInitialObjects:^(NSDictionary *info) {
NSLog(#"Managed to get initial infos");
reply(info);
} failure:^(NSError *error) {
NSLog(#"Fail : %#", error);
reply(#{#"error": error});
}];
}
}
+ (void) watchInitialObjects:(void (^)(NSDictionary *info))success failure:(void (^)(NSError *error))failure {
NSDictionary *parameters = #{#"site" : #(14), #"limit" : #(10)};
[AppDelegate requestDiapoListWithParams:parameters success:^(NSArray *items) {
if ([items count] == 0)
{
NSError *error = [NSError errorWithDomain:#"com.domain.app" code:404 userInfo:nil];
failure(error);
return;
}
Diapo *firstDiapo = [items firstObject];
[AppDelegate requestDiapoDetailWithDiapo:firstDiapo success:^(Diapo *diapo) {
if (!diapo)
{
NSError *error = [NSError errorWithDomain:#"com.domain.app" code:404 userInfo:nil];
failure(error);
return;
}
NSDictionary *result = #{
#"firstDiapo" : diapo,
#"diapos" : items
};
success(result);
} failure:^(NSError *error) {
failure(error);
}];
} failure:^(NSError *error) {
failure(error);
}];
}
In the watchKitHandler, I call watchInitialObjects to get the diaporamas array and the first diaporama's informations.
In the watchInitialObjects, I make a first network call to get the array, and on success, I make an other network call to get the firs diaporama informations.
To make the calls and map the JSON into objects, I use RESTKit
I really don't get what could be the error =x
UPDATE
I forgot to write the error I get, here it is :
Error Domain=com.apple.watchkit.errors Code=2 "The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]" UserInfo=0x7fcb53e12830 {NSLocalizedDescription=The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]}
And I kept trying to know why I get this error, and I think I found it:
It seems that there is a (very little) timeout to do the work in the containing app. But I mapped the JSON data I received directly in the containing app and then, send those custom objects in the reply(). But when I removed the mapping part, it worked well !
So...that's why I think that was the problem =X
Does anybody could approve my thoughts or corrects me ?
After hours of searching and testing different codes, I finally found my problem...and it's obvious when we read the Apple documentation about 'application:handleWatchKitExtensionRequest:reply:' seriously...
here is the answer : (it's in the documentation)
The contents of the dictionary must be serializable to a property list file.
Which means that objects can ONLY be dictionaries, arrays, strings, numbers (integer and float), dates, binary data, or Boolean values
...I feel dumb ><
I currently have a share extension set up that will upload an image selected from the Photo app to a server. This works fine using the code below.
int fileNum=10;
NSItemProvider *attachment = inputItem.attachments[0];
if ([attachment hasItemConformingToTypeIdentifier:(NSString*)kUTTypeImage])
{
[attachment loadItemForTypeIdentifier:(NSString*)kUTTypeImage options:nil completionHandler:^(id item,NSError *error)
{
if (item)
{
NSLog (#"image %#",item);
//upload image here
NSData *data=[NSData dataWithContentsOfURL:item];
activityRecord.activityType=#"Images";
AppRecord *appRecord=[[AppRecord alloc] init];
appRecord.fileName=[NSString stringWithFormat:#"activity_%#%i(%i).jpg",activityRecord.supplierID,activityRecord.activityID,fileNum];
appRecord.fileBytes=data;
[fileRecords addObject:appRecord];
activityRecord.activityFiles=fileRecords;
[[Settings getInstance] uploadActivityRecord:activityRecord withDelegate:self];
[self.extensionContext completeRequestReturningItems:#[] completionHandler:nil];
}
}];
}
I had a previous problem where the loadItemForTypeIdentifier method wasn't being called, and it was resolved by calling completeRequestReturningItems within the completion block.
The problem I have now is that if I want to upload multiple files then I need to call loadItemForTypeIdentifier within a for loop (for each image) but how can I do that if the completeRequestReturningItems method will be called after the first image/item?
Many Thanks
Paul
I ran into the same problem recently and was able to resolve it by adding a counter and counting down as the images successfully completed their block. Within the loadItemForTypeIdentifier completion block I then check to see if all items have been called before calling the completeRequestReturningItems within a dispatch_once block (just for safety's sake).
__block NSInteger imageCount;
static dispatch_once_t oncePredicate;
NSItemProvider *attachment = inputItem.attachments[0];
if ([attachment hasItemConformingToTypeIdentifier:(NSString*)kUTTypeImage])
{
[attachment loadItemForTypeIdentifier:(NSString*)kUTTypeImage options:nil completionHandler:^(NSData *item ,NSError *error)
{
if (item)
{
// do whatever you need to
imageCount --;
if(imageCount == 0){
dispatch_once(&oncePredicate, ^{
[self.extensionContext completeRequestReturningItems:#[] completionHandler:nil];
});
}
}
}];
}
I can't say I feel like this is an overly elegant solution however, so if someone knows of a more appropriate way of handling this common use case I'd love to hear about it.