简体   繁体   English

用于在共享扩展和iOS应用之间共享文件路径/文件的代码

[英]Code to share file path/file between a share extension and iOS app

I have added a share extension for my app say SAMPLE (already exists on the app store), called lets say SAMPLESHARE. 我为我的应用添加了一个共享扩展名,例如SAMPLE(已存在于应用商店中),称为SAMPLESHARE。 Whenever a user, say takes a picture and tries to share it, I want them to go through a view controller of an Open In functionality, and not get the Post dialogue from Apple, basically bypassing it. 每当用户说拍照并试图分享它时,我希望他们通过Open In功能的视图控制器,而不是从Apple获得Post对话,基本上绕过它。 So I am trying to share the picture between the share extension and my app, by creating an app group that is shared between the app and plugin and then passing the file paths to the openURL of the application delegate of my app. 所以我试图在共享扩展和我的应用程序之间分享图片,创建一个在应用程序和插件之间共享的应用程序组,然后将文件路径传递给我的应用程序的应用程序委托的openURL。

So in my main application delegate I have 所以在我的主要应用代表中我有

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{

    return [[SAMPLEExternalFileHandler shared] handleExternalFileURL:url];
}

which basically I use for checking everytime if I have a URL file path that needs to open a different flow. 如果我有一个需要打开不同流的URL文件路径,我基本上每次都会检查它。

In my SHAREEXTENSION I have 在我的SHAREEXTENSION中,我有

#import "ShareViewController.h"
#import <MobileCoreServices/UTCoreTypes.h>
//Macro to hide post dialog or not, if defined, will be hidden, comment during debugging
#define HIDE_POST_DIALOG

@interface ShareViewController ()

@end

@implementation ShareViewController

NSUInteger m_inputItemCount = 0; // Keeps track of the number of attachments we have opened asynchronously.
NSString * m_invokeArgs = NULL;  // A string to be passed to your AIR app with information about the attachments.
NSString * APP_SHARE_GROUP = @"group.com.SAMPLE.SAMPLESHAREPLUGIN";
const NSString * APP_SHARE_URL_SCHEME = @"SAMPLE";
CGFloat m_oldAlpha = 1.0; // Keeps the original transparency of the Post dialog for when we want to hide it.

- (BOOL)isContentValid {
    // Do validation of contentText and/or NSExtensionContext attachments here
    return YES;
}

- ( void ) didSelectPost
{
#ifdef HIDE_POST_DIALOG
    return;
#endif
    [ self passSelectedItemsToApp ];
    // Note: This call is expected to be made here. Ignore it. We'll tell the host we are done after we've invoked the app.
    //    [ self.extensionContext completeRequestReturningItems: @[] completionHandler: nil ];
}
- ( void ) addImagePathToArgumentList: ( NSString * ) imagePath
{
    assert( NULL != imagePath );

    // The list of arguments we will pass to the AIR app when we invoke it.
    // It will be a comma-separated list of file paths: /path/to/image1.jpg,/path/to/image2.jpg
    if ( NULL == m_invokeArgs )
    {
        m_invokeArgs = imagePath;
    }
    else
    {
        m_invokeArgs = [ NSString stringWithFormat: @"%@,%@", m_invokeArgs, imagePath ];
    }
}

- ( NSString * ) saveImageToAppGroupFolder: ( UIImage * ) image
                                imageIndex: ( int ) imageIndex
{
    assert( NULL != image );

    NSData * jpegData = UIImageJPEGRepresentation( image, 1.0 );

    NSURL * containerURL = [ [ NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier: APP_SHARE_GROUP ];
    NSString * documentsPath = containerURL.path;

    // Note that we aren't using massively unique names for the files in this example:
    NSString * fileName = [ NSString stringWithFormat: @"image%d.jpg", imageIndex ];

    NSString * filePath = [ documentsPath stringByAppendingPathComponent: fileName ];
    [ jpegData writeToFile: filePath atomically: YES ];

    return filePath;
}

- ( void ) passSelectedItemsToApp
{
    NSExtensionItem * item = self.extensionContext.inputItems.firstObject;

    // Reset the counter and the argument list for invoking the app:
    m_invokeArgs = NULL;
    m_inputItemCount = item.attachments.count;

    // Iterate through the attached files
    for ( NSItemProvider * itemProvider in item.attachments )
    {
        // Check if we are sharing a JPEG
        if ( [ itemProvider hasItemConformingToTypeIdentifier: ( NSString * ) kUTTypeImage ] )
        {
            // Load it, so we can get the path to it
            [ itemProvider loadItemForTypeIdentifier: ( NSString * ) kUTTypeImage
                                             options: NULL
                                   completionHandler: ^ ( UIImage * image, NSError * error )
             {
                 static int itemIdx = 0;

                 if ( NULL != error )
                 {
                     NSLog( @"There was an error retrieving the attachments: %@", error );
                     return;
                 }

                 // The app won't be able to access the images by path directly in the Camera Roll folder,
                 // so we temporary copy them to a folder which both the extension and the app can access:
                 NSString * filePath = [ self saveImageToAppGroupFolder: image imageIndex: itemIdx ];

                 // Now add the path to the list of arguments we'll pass to the app:
                 [ self addImagePathToArgumentList: filePath ];

                 // If we have reached the last attachment, it's time to hand control to the app:
                 if ( ++itemIdx >= m_inputItemCount )
                 {
                     [ self invokeApp: m_invokeArgs ];
                 }
             } ];
        }
    }
}
- ( void ) invokeApp: ( NSString * ) invokeArgs
{
    // Prepare the URL request
    // this will use the custom url scheme of your app
    // and the paths to the photos you want to share:
    NSString * urlString = [ NSString stringWithFormat: @"%@://%@", APP_SHARE_URL_SCHEME, ( NULL == invokeArgs ? @"" : invokeArgs ) ];
    NSURL * url = [ NSURL URLWithString: urlString ];

    NSString *className = @"UIApplication";
    if ( NSClassFromString( className ) )
    {
        id object = [ NSClassFromString( className ) performSelector: @selector( sharedApplication ) ];
        [ object performSelector: @selector( openURL: ) withObject: url ];
    }

    // Now let the host app know we are done, so that it unblocks its UI:
    [ super didSelectPost ];
}

#ifdef HIDE_POST_DIALOG
- ( NSArray * ) configurationItems
{
    // Comment out this whole function if you want the Post dialog to show.
    [ self passSelectedItemsToApp ];

    // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
    return @[];
}
#endif


#ifdef HIDE_POST_DIALOG
- ( void ) willMoveToParentViewController: ( UIViewController * ) parent
{
    // This is called at the point where the Post dialog is about to be shown.
    // Make it transparent, so we don't see it, but first remember how transparent it was originally:

    m_oldAlpha = [ self.view alpha ];
    [ self.view setAlpha: 0.0 ];
}
#endif

#ifdef HIDE_POST_DIALOG
- ( void ) didMoveToParentViewController: ( UIViewController * ) parent
{
    // Restore the original transparency:
    [ self.view setAlpha: m_oldAlpha ];
}
#endif
#ifdef HIDE_POST_DIALOG
- ( id ) init
{
    if ( self = [ super init ] )
    {
        // Subscribe to the notification which will tell us when the keyboard is about to pop up:
        [ [ NSNotificationCenter defaultCenter ] addObserver: self selector: @selector( keyboardWillShow: ) name: UIKeyboardWillShowNotification    object: nil ];
    }

    return self;
}
#endif
#ifdef HIDE_POST_DIALOG
- ( void ) keyboardWillShow: ( NSNotification * ) note
{
    // Dismiss the keyboard before it has had a chance to show up:
    [ self.view endEditing: true ];
}
#endif
@end

And my info.plist for the extension is 我的扩展名为info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>en</string>
    <key>CFBundleDisplayName</key>
    <string>SAMPLESHARE</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIdentifier</key>
    <string>com.org.SAMPLE.$(PRODUCT_NAME:rfc1034identifier)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>$(PRODUCT_NAME)</string>
    <key>CFBundlePackageType</key>
    <string>XPC!</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleVersion</key>
    <string>1</string>
    <key>NSExtension</key>
    <dict>
        <key>NSExtensionAttributes</key>
         <dict>
        <key>NSExtensionActivationRule</key>
        <dict>
            <key>NSExtensionActivationSupportsImageWithMaxCount</key>
            <integer>1</integer>
        </dict>
    </dict>
        <key>NSExtensionMainStoryboard</key>
        <string>MainInterface</string>
        <key>NSExtensionPointIdentifier</key>
        <string>com.apple.share-services</string>
    </dict>
</dict>
</plist>

I have basically used some commons license code off the internet(reputed site), which claims to have passed the app store review process. 我基本上使用了互联网(知名网站)的一些公共许可证代码,声称已通过应用商店审核流程。

There are two workarounds in the code, one is to call the OpenURL from the share extension(which from scouring SO seems like is still not possible normally without workarounds on iOS 8.3 and above) and the second is to hide the post dialogue and the keyboard that apple provides by default when anyone clicks on share. 代码中有两种解决方法,一种是从共享扩展中调用OpenURL(从淘汰中看起来似乎仍然不可能在iOS 8.3及更高版本上没有解决方法),第二种是隐藏帖子对话和键盘当任何人点击共享时,苹果默认提供。 This works. 这有效。

I have two questions 我有两个问题

1.) Will this be accepted on the app store? -- basically how are apps like facebook/whatsapp doing it and they are being accepted?
2.) Whenever I run this, it says `NSExtensionActivationRule` if set to `TRUEPREDICATE` will be rejected in review, what should the value be? 

UPDATE: 更新:

So scouring through the documentation I have found a fix for question 2, and changed this. 通过文档搜索,我找到了问题2的修复,并改变了这一点。 Now everything works, and there is no TRUEPREDICATE , will this be accepted on the store or is there another way to do this? 现在一切正常,而且没有TRUEPREDICATE ,这会被商店接受还是有其他方法可以做到这一点?

UPDATE 2: 更新2:

I have now used NSUserDefaults to pass the data from the extension to the app, guess that is also one requirement for sharing data. 我现在使用NSUserDefaults将数据从扩展程序传递到应用程序,猜测这也是共享数据的一个要求。

UPDATE UPDATE

The app was accepted in the review using NSUSERDEFAULTS as the message passing mechanism. 在使用NSUSERDEFAULTS作为消息传递机制的评论中接受了该应用程序。 Here are the steps. 这是步骤。

1.) Share extension: 1.)分享扩展:

#import "ShareViewController.h"
#import <MobileCoreServices/UTCoreTypes.h>
//Macro to hide post dialog or not, if defined, will be hidden, comment during debugging
#define HIDE_POST_DIALOG

@interface ShareViewController ()

@end

@implementation ShareViewController

NSUInteger m_inputItemCount = 0; // Keeps track of the number of attachments we have opened asynchronously.
NSString * m_invokeArgs = NULL;  // A string to be passed to your AIR app with information about the attachments.
NSString * APP_SHARE_GROUP = @"group.com.schemename.nameofyourshareappgroup";
const NSString * APP_SHARE_URL_SCHEME = @"schemename";
CGFloat m_oldAlpha = 1.0; // Keeps the original transparency of the Post dialog for when we want to hide it.

- (BOOL)isContentValid {
    // Do validation of contentText and/or NSExtensionContext attachments here
    return YES;
}

- ( void ) didSelectPost
{
#ifdef HIDE_POST_DIALOG
    return;
#endif

    [ self passSelectedItemsToApp ];
    // Note: This call is expected to be made here. Ignore it. We'll tell the host we are done after we've invoked the app.
    //    [ self.extensionContext completeRequestReturningItems: @[] completionHandler: nil ];
}
- ( void ) addImagePathToArgumentList: ( NSString * ) imagePath
{
    assert( NULL != imagePath );

    // The list of arguments we will pass to the AIR app when we invoke it.
    // It will be a comma-separated list of file paths: /path/to/image1.jpg,/path/to/image2.jpg
    if ( NULL == m_invokeArgs )
    {
        m_invokeArgs = imagePath;
    }
    else
    {
        m_invokeArgs = [ NSString stringWithFormat: @"%@,%@", m_invokeArgs, imagePath ];
    }
}

- ( NSString * ) saveImageToAppGroupFolder: ( UIImage * ) image
                                imageIndex: ( int ) imageIndex
{
    assert( NULL != image );

    NSData * jpegData = UIImageJPEGRepresentation( image, 1.0 );

    NSURL * containerURL = [ [ NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier: APP_SHARE_GROUP ];
    NSString * documentsPath = containerURL.path;

    // Note that we aren't using massively unique names for the files in this example:
    NSString * fileName = [ NSString stringWithFormat: @"image%d.jpg", imageIndex ];

    NSString * filePath = [ documentsPath stringByAppendingPathComponent: fileName ];
    [ jpegData writeToFile: filePath atomically: YES ];

    //Mahantesh -- Store image url to NSUserDefaults

    NSUserDefaults *defaults=[[NSUserDefaults alloc] initWithSuiteName:@"group.com.schemename.nameofyourshareappgroup"];
    [defaults setObject:filePath forKey:@"url"];
    [defaults synchronize];

    return filePath;
}

- ( void ) passSelectedItemsToApp
{
    NSExtensionItem * item = self.extensionContext.inputItems.firstObject;

    // Reset the counter and the argument list for invoking the app:
    m_invokeArgs = NULL;
    m_inputItemCount = item.attachments.count;

    // Iterate through the attached files
    for ( NSItemProvider * itemProvider in item.attachments )
    {
        // Check if we are sharing a Image
        if ( [ itemProvider hasItemConformingToTypeIdentifier: ( NSString * ) kUTTypeImage ] )
        {
            // Load it, so we can get the path to it
            [ itemProvider loadItemForTypeIdentifier: ( NSString * ) kUTTypeImage
                                             options: NULL
                                   completionHandler: ^ ( UIImage * image, NSError * error )
             {
                 static int itemIdx = 0;

                 if ( NULL != error )
                 {
                     NSLog( @"There was an error retrieving the attachments: %@", error );
                     return;
                 }

                 // The app won't be able to access the images by path directly in the Camera Roll folder,
                 // so we temporary copy them to a folder which both the extension and the app can access:
                 NSString * filePath = [ self saveImageToAppGroupFolder: image imageIndex: itemIdx ];

                 // Now add the path to the list of arguments we'll pass to the app:
                 [ self addImagePathToArgumentList: filePath ];

                 // If we have reached the last attachment, it's time to hand control to the app:
                 if ( ++itemIdx >= m_inputItemCount )
                 {
                     [ self invokeApp: m_invokeArgs ];
                 }
             } ];
        }
    }
}
- ( void ) invokeApp: ( NSString * ) invokeArgs
{
    // Prepare the URL request
    // this will use the custom url scheme of your app
    // and the paths to the photos you want to share:
    NSString * urlString = [ NSString stringWithFormat: @"%@://%@", APP_SHARE_URL_SCHEME, ( NULL == invokeArgs ? @"" : invokeArgs ) ];
    NSURL * url = [ NSURL URLWithString: urlString ];

    NSString *className = @"UIApplication";
    if ( NSClassFromString( className ) )
    {
        id object = [ NSClassFromString( className ) performSelector: @selector( sharedApplication ) ];
        [ object performSelector: @selector( openURL: ) withObject: url ];
    }

    // Now let the host app know we are done, so that it unblocks its UI:
    [ super didSelectPost ];
}

#ifdef HIDE_POST_DIALOG
- ( NSArray * ) configurationItems
{
    // Comment out this whole function if you want the Post dialog to show.
    [ self passSelectedItemsToApp ];

    // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
    return @[];
}
#endif


#ifdef HIDE_POST_DIALOG
- ( void ) willMoveToParentViewController: ( UIViewController * ) parent
{
    // This is called at the point where the Post dialog is about to be shown.
    // Make it transparent, so we don't see it, but first remember how transparent it was originally:

    m_oldAlpha = [ self.view alpha ];
    [ self.view setAlpha: 0.0 ];
}
#endif

#ifdef HIDE_POST_DIALOG
- ( void ) didMoveToParentViewController: ( UIViewController * ) parent
{
    // Restore the original transparency:
    [ self.view setAlpha: m_oldAlpha ];
}
#endif
#ifdef HIDE_POST_DIALOG
- ( id ) init
{
    if ( self = [ super init ] )
    {
        // Subscribe to the notification which will tell us when the keyboard is about to pop up:
        [ [ NSNotificationCenter defaultCenter ] addObserver: self selector: @selector( keyboardWillShow: ) name: UIKeyboardWillShowNotification    object: nil ];
    }

    return self;
}
#endif
#ifdef HIDE_POST_DIALOG
- ( void ) keyboardWillShow: ( NSNotification * ) note
{
    // Dismiss the keyboard before it has had a chance to show up:
    [ self.view endEditing: true ];
}
#endif
@end
  1. In the openURL method of your application delegate 在应用程序委托的openURL方法中

      //Slartibartfast -- For the case where we are opening app from an extension NSString *STATIC_FILE_HANDLE = @"file://"; //If app is opened from share extension, do the following /* 1.) Get path of shared file from NSUserDefaults 2.) Get data from file and store in some variable 3.) Create a new accesible unique file path 4.) Dump data created into this file. */ NSUserDefaults *defaults=[[NSUserDefaults alloc] initWithSuiteName:YOURAPP_STATIC_APP_GROUP_NAME]; NSString *path=nil; if(defaults) { [defaults synchronize]; path = [defaults stringForKey:@"url"]; } if(path.length != 0) { NSData *data; //Get file path from url shared NSString * newFilePathConverted = [STATIC_FILE_HANDLE stringByAppendingString:path]; url = [ NSURL URLWithString: newFilePathConverted ]; data = [NSData dataWithContentsOfURL:url]; //Create a regular access path because this app cant preview a shared app group path NSString *regularAccessPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSString *uuid = [[NSUUID UUID] UUIDString]; //Copy file to a jpg image(ignore extension, will convert from png) NSString *uniqueFilePath= [ NSString stringWithFormat: @"/image%@.jpg", uuid]; regularAccessPath = [regularAccessPath stringByAppendingString:uniqueFilePath]; NSString * newFilePathConverted1 = [STATIC_FILE_HANDLE stringByAppendingString:regularAccessPath]; url = [ NSURL URLWithString: newFilePathConverted1 ]; //Dump existing shared file path data into newly created file. [data writeToURL:url atomically:YES]; //Reset NSUserDefaults to Nil once file is copied. [defaults setObject:nil forKey:@"url"]; } //Do what you want } 

Thanks to EasyNativeExtensions for pointers 感谢EasyNativeExtensions指针

Your question is a bit messed up but if it is about passing data from one app to another app you have a great solution for that which is UIPasteboard 您的问题有点混乱,但如果是将数据从一个应用程序传递到另一个应用程序,那么您有一个很好的解决方案就是UIPasteboard

If you don't have any problem for jumping between apps using custom URL handlers then you have 2 more steps left. 如果您使用自定义URL处理程序在应用程序之间跳转没有任何问题,那么您还剩2个步骤。

Step 1 步骤1
In your first application which is responsible for passing data implement these methods and then call custom URL. 在您的第一个负责传递数据的应用程序中实现这些方法,然后调用自定义URL。

UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
[[UIPasteboard generalPasteboard] setImage:passImage];


Step 2 第2步
In your target view controller simple call UIPasteboard again and get the data from it. 在目标视图控制器中,再次调用UIPasteboard并从中获取数据。

UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
   UIImage *getImage = pasteboard.image;


Please note, that you pass a UIImage and you get it in the same type 请注意,您传递了UIImage并且您获得了相同的类型

  1. If you don't want show dialog default from apple. 如果您不希望显示对话框默认来自apple。 Should be inherit from UIViewController not @interface ShareViewController : SLComposeServiceViewController 应该继承自UIViewController而不是@interface ShareViewController:SLComposeServiceViewController
  2. In Apple developer document, don't permit Extensions app open directly Containing app except Today extension app. 在Apple开发人员文档中,不要允许Extensions应用程序直接打开包含除今日扩展应用程序之外的应用程序。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM