Sharing images/videos to other apps through UIActivityViewController shows shared items twice in iOS
Asked Answered
L

2

6

I'm trying to share some images and videos to other apps(like FB, WhatsApp) using UIActivityViewController. I have sub-classed UIActivityItemProvider, and on calling -(id)item methods, I'm processing the images/videos and saving in documents directory. Then I am returning the file paths as NSURLs. My problem is that I'm not able to find a way to send multiple file URLs at the same time.

Below are the approaches I took to return urls from -(id)item method;

  1. As an NSArray of NSURL objects. DOES NOT WORK. When the target app popup comes, it is empty always.
  2. As a NSDictionary, in which NSURL objects are the values and keys could be anything. PROBLEMATIC: The target app popup shows all items, but TWICE! I experimented with the dictionary a lot, but couldn't find a way to solve this.

Simply returning an NSURL object from -(id)item method works fine for single file. But, I have to share multiple items. Array doesn't work, Dictionary is duplicating shared items.

Can someone please tell me what I'm doing wrong here?


UPDATE 1:

This is how I show the UIActivityViewController.

CustomItemProvider *provider = [[CustomItemProvider alloc] initWithPlaceholderItem:[UIImage imageNamed:@"ios-59.png"]];

UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[provider] applicationActivities:nil];

activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError)
{
    if(completed)
    {
        NSLog(@"Activity Completed");
    }
    else
    {
        NSLog(@"Activity Cancelled");
    }
};
[self presentViewController:activityViewController animated:YES completion:^{}];

The UIActivityItemProvider implementation is as given below; The URLs are valid and there are images/videos at those locations.

@interface CustomItemProvider : UIActivityItemProvider

@end

@implementation CustomItemProvider
- (id)item
{
     NSURL *url1 = [NSURL fileURLWithPath:@"file one url"];
     NSURL *url2 = [NSURL fileURLWithPath:@"file two url"];
     NSURL *url3 = [NSURL fileURLWithPath:@"file three url"];

     return @{@"item1":url1, @"item2":url2, @"item3":url3};    //As NSDictionary. This causes 6 items to be shared; all files twice.

     //return @[url1, url2, url3];  //As NSArray
}
@end

UPDATE 2:

The linked question is different. I don't want to send the files directly to the UIActivityViewController as parameter to initWithActivityItems:. The reason is that there could be multiple video files, which will cause memory warning and a crash. Also, I will be manipulating the files before sending it to target app(in the -(id)item method, which I have not shown here), hence I need UIActivityItemProvider to process the files in background.

Leolaleoline answered 27/7, 2016 at 12:41 Comment(7)
It's hard to guess without seeing what you're actually doing. What does the failing code look like?Co
@PhillipMills, I have edited the question and added the method.Leolaleoline
Possible duplicate of How to share multiple file via UIDocumentInteractionController?Palestrina
@MaxvonHippel The linked post is different from what I'm trying to achieve. Please see the UPDATE2 in the edited question.Leolaleoline
Do you know that it will cause a memory warning and a crash? It wouldn't surprise me if Apple compiles the code such that it optimized and handles one file at a time rather than just like putting seven videos in RAM at once or something. Also if it's just sharing the URI to the internal file I don't see why that would take much memory at all!Palestrina
It will actually. The videos I am sharing are around 600MB in size and I will be sharing around 6-10 files at a time. So the total memory for those files will be in the range 3.6GB-6.0GB. To pass these files into the mentioned method, we need to load it in memory and create an array. This crashes the app. I have tested the same in iPhone6S and it crashes. Apple also suggests the same approach to be followed in this scenario. Please refer developer.apple.com/library/ios/documentation/UIKit/Reference/…Leolaleoline
As for sharing the URI, I need to process the video before giving it to the target app, hence directly passing them to the UIActivityViewController is not an option for me unfortunately.Leolaleoline
L
0

UPDATE: This approach was completely wrong and will lead to unexpected results in some scenarios. Sorry if this answer mislead anyone.

The correct approach is to create one UIActivityItemProvider object for each item to be shared. And return the item to be shared from the corresponding provider object. Sorry that I couldn't update this answer before.

Good day everyone!


Okay, finally I got it. This has been driving me crazy and I couldn't find any proper documentation/answers anywhere.

The solution was to return a dictionary back with NSItemProvider objects as values. The keys for the same could be anything that is not same as other keys.

If anyone is after the same problem, find the solution below;

@interface CustomItemProvider : UIActivityItemProvider
{
     NSCondition *_condition;
     NSArray *_filesToShare;
}
@end

@implementation CustomItemProvider
- (id)item
{
    _fetchInProgress = YES; 
    //Here start fetching/processing videos/photos in ANOTHER THREAD. Once the fetching/processing is complete, set the value of _fetchInProgress to NO.

    //If you want to show progress, display progress view in MAIN THREAD here.

    [_condition lock];
    while(_fetchInProgress)
    {
        [_condition wait];
    }
    [_condition unlock];

    //Here I am assuming that the files are processed and are saved to documents/cache directory and their file-paths are in _filesToShare array.

     NSMutableDictionary *itemsDict = [NSMutableDictionary dictionaryWithCapacity:[_filesToShare count]];
     for (int i = 0; i < [_filesToShare count]; i++)
     {
         NSURL *url = [NSURL fileURLWithPath:[_filesToShare objectAtIndex:i]];
         NSItemProvider *item = [[NSItemProvider alloc] initWithContentsOfURL:url];
         [itemsDict setObject:item forKey:@(i)];
     }

     return itemsDict;
}
@end

Cheers!!!

Leolaleoline answered 1/8, 2016 at 7:43 Comment(0)
E
1

Share image with other apps: Swift 3 code-

    let image = UIImage(named: "yourimage")
    let objectsToShare = [image]
    let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
    present(activityVC, animated: true, completion: nil)
Ellery answered 9/10, 2017 at 11:12 Comment(0)
L
0

UPDATE: This approach was completely wrong and will lead to unexpected results in some scenarios. Sorry if this answer mislead anyone.

The correct approach is to create one UIActivityItemProvider object for each item to be shared. And return the item to be shared from the corresponding provider object. Sorry that I couldn't update this answer before.

Good day everyone!


Okay, finally I got it. This has been driving me crazy and I couldn't find any proper documentation/answers anywhere.

The solution was to return a dictionary back with NSItemProvider objects as values. The keys for the same could be anything that is not same as other keys.

If anyone is after the same problem, find the solution below;

@interface CustomItemProvider : UIActivityItemProvider
{
     NSCondition *_condition;
     NSArray *_filesToShare;
}
@end

@implementation CustomItemProvider
- (id)item
{
    _fetchInProgress = YES; 
    //Here start fetching/processing videos/photos in ANOTHER THREAD. Once the fetching/processing is complete, set the value of _fetchInProgress to NO.

    //If you want to show progress, display progress view in MAIN THREAD here.

    [_condition lock];
    while(_fetchInProgress)
    {
        [_condition wait];
    }
    [_condition unlock];

    //Here I am assuming that the files are processed and are saved to documents/cache directory and their file-paths are in _filesToShare array.

     NSMutableDictionary *itemsDict = [NSMutableDictionary dictionaryWithCapacity:[_filesToShare count]];
     for (int i = 0; i < [_filesToShare count]; i++)
     {
         NSURL *url = [NSURL fileURLWithPath:[_filesToShare objectAtIndex:i]];
         NSItemProvider *item = [[NSItemProvider alloc] initWithContentsOfURL:url];
         [itemsDict setObject:item forKey:@(i)];
     }

     return itemsDict;
}
@end

Cheers!!!

Leolaleoline answered 1/8, 2016 at 7:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.