react-native-share / react-native-share

Social share, sending simple data to other apps.
https://react-native-share.github.io/react-native-share
MIT License
3.64k stars 946 forks source link

Share PHAsset #634

Closed RocKhalil closed 3 years ago

RocKhalil commented 4 years ago

About

I'm trying to share images from my CameraRoll using @react-native-community/react-native-share and @react-native-community/cameraroll

Code Preview

import CameraRoll from '@react-native-community/cameraroll'
import Share from 'react-native-share'

const photos = await CameraRoll.getPhotos({
  first: 10,
  assetType: 'All',
})

const photo = photos[0]

const shareOptions = {
  url: photo.image.uri,
  type: photo.type,
  title: 'My Photo',
  subject: 'Check out what I found!'
}

Share.open(shareOptions)

This isn't the actual one, but works fine for demo purposes.

Question

On android, it's working perfectly. However, on iOS, we get from photo.image.uri a PHAsset url of the asset. Right now, when the share dialog opens, we can't see the file that is being shared and we only see 001 which is the PHAsset's last file path. Is there any way to share the ph://.... image or video directly ?

jgcmarins commented 4 years ago

Hello @RocKhalil, it should be possible. But maybe is there a way to tell react-native-cameraroll to return another type for photo.image.uri

RocKhalil commented 4 years ago

@jgcmarins I wrote a small hack for it right now (within the cameraroll) in order to keep going with the development; but I'll try writing a fix to allow ph asset to be read directly.

Maybe you can get inspired by this for the react-native-share updates

if ([assetMediaTypeLabel isEqual:@"image"]) {
  [asset requestContentEditingInputWithOptions:nil completionHandler:^(PHContentEditingInput * _Nullable contentEditingInput, NSDictionary * _Nonnull info) {
    url = contentEditingInput.fullSizeImageURL.absoluteString;
  }];
} else {
  [[PHImageManager defaultManager] requestAVAssetForVideo:asset options:nil resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
    AVURLAsset *urlAsset = (AVURLAsset*)asset;
    url = [[urlAsset URL] absoluteString];
  }];
}

do { } while( [url isEqual:@""] );

I know that the code is not performance oriented, but I need to move on from this task and will get back to this one as soon as possible.

jgcmarins commented 4 years ago

@RocKhalil great to hear that. Pull requests are welcome. Feel free to open one with this improvement. I can help with code review.

RocKhalil commented 4 years ago

@jgcmarins I tried adding the above piece of code to RNShare directly; here's where I currently stand:

#import <Photos/Photos.h>

RCT_EXPORT_METHOD(open:(NSDictionary *)options
                  failureCallback:(RCTResponseErrorBlock)failureCallback
                  successCallback:(RCTResponseSenderBlock)successCallback)
{
  ....
  if ([URL.scheme.lowercaseString isEqualToString:@"data"]) {
    ...
  } else if ([URL.scheme.lowercaseString isEqualToString:@"ph"]) {
    NSString *assetIdentifier = [urlsArray[i] stringByReplacingOccurrencesOfString: @"ph://" withString: @""];
    PHFetchResult *fetchResult = [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers: @[assetIdentifier] options:nil];
    PHAsset *asset = fetchResult.firstObject;
    NSString __block *url = @"";

    if (asset){
      switch (asset.mediaType) {
        case PHAssetMediaTypeVideo:
          [[PHImageManager defaultManager] requestAVAssetForVideo:asset options:nil resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
            AVURLAsset *urlAsset = (AVURLAsset*)asset;
            url = [[urlAsset URL] absoluteString];
          }];
          break;
        case PHAssetMediaTypeImage:
           [asset requestContentEditingInputWithOptions:nil completionHandler:^(PHContentEditingInput * _Nullable contentEditingInput, NSDictionary * _Nonnull info) {
                 url = contentEditingInput.fullSizeImageURL.absoluteString;
               }
            ];
          break;
        default:
          RCTLogError(@"Asset type can't be shared");
          return;
      }

      do { } while( [url isEqual:@""] );
      [items addObject: url];
    }
  } else {
     [items addObject:URL];
  }

  ....
}

Now the issue is that do { } while( [url isEqual:@""] ) is executed on the main_queue and the app is freezing. I'm not that advanced is Objective-C and not sure how to await a block for completing instead of doing this method.

If you can help me with this, I'll test the solution and create a PR asap.

Thanks :-)

RocKhalil commented 4 years ago

@jgcmarins the previous answer had some issues (not sharing correctly), so I wrote a better fix for PHAsset sharing:

#import <UIKit/UIWindow.h>
#import <UIKit/UIActivityViewController.h>

RCT_REMAP_METHOD(getShareModal, assetURI:(NSString *)assetURI)
{
  NSMutableArray *activityItems = [NSMutableArray array];

  // image Loader
  PHImageManager *im = [PHImageManager defaultManager];
  PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
  options.synchronous = YES;
  options.version = PHImageRequestOptionsVersionCurrent;
  options.deliveryMode = PHImageRequestOptionsDeliveryModeOpportunistic;
  options.resizeMode = PHImageRequestOptionsResizeModeNone;

  PHFetchResult<PHAsset *> *fetchResult;
  NSString *const localIdentifier = [assetURI substringFromIndex:@"ph://".length];
  fetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[localIdentifier] options:nil];
  PHAsset *const _Nonnull asset = [fetchResult firstObject];

  // load image
  [im requestImageDataForAsset:asset options:options resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info)
   {
       [activityItems addObject:imageData];
   }];

  // generate the activity view
  UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil];
  // prevent sharing to gallery again
  activityVC.excludedActivityTypes = @[UIActivityTypeSaveToCameraRoll];

  // get the top root view controller
  UIViewController *topRootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
  while (topRootViewController.presentedViewController)
  {
      topRootViewController = topRootViewController.presentedViewController;
  }

  // present it
  [topRootViewController presentViewController:activityVC animated:YES completion:nil];
}

This way, whoever is calling the API will only do: getShareModal('ph://....') and the modal will show.

jgcmarins commented 4 years ago

That's great. Does it makes sense to support PHAsset. I mean, does Apple is moving around to this type for URIs?

RocKhalil commented 4 years ago

@jgcmarins mainly yes.

It will also give compatibility to share images retrieved from @react-native-community/cameraroll. All the images that come from the system are PHAsset and should be handled this way..

scgough commented 4 years ago

👍 This looks like a cleaner version of a fix I had to write to get the share working for image and video phasset urls on iOS. I used dispatch_semaphore... to signal when to continue after getting what I need from the asset.

I'm just in the process of extending the IG share (and stories) to also cater for ph:// urls too.

MateusAndrade commented 4 years ago

This looks like a cleaner version of a fix I had to write to get the share working for image and video phasset urls on iOS. I used dispatch_semaphore... to signal when to continue after getting when I need from the asset.

I'm just in the process of extending the IG share (and stories) to also cater for ph:// urls too.

This would be a great to rn-share. Do you need any help on that?

scgough commented 4 years ago

This would be a great to rn-share. Do you need any help on that?

Cheers! 👍🏻 I might to perhaps tighten up the Obj-C code as I don’t have lots of experience in it. I’ll get it working for what I’m doing and then put a PR together. Just want to get stories with stickers working again!

github-actions[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. You may also mark this issue as a "discussion" and i will leave this open

mrousavy commented 11 months ago

Hey! Did this end up being merged into react-native-share? Can you share ph:// assets now?