2015-08-26 11 views
14

Dodałem rozszerzenie udziału dla mojej aplikacji, powiedz SAMPLE (już istnieje w sklepie z aplikacjami), nazwijmy powiedzmy SAMPLESHARE. Za każdym razem, gdy użytkownik, powiedzmy, robi zdjęcie i próbuje je udostępnić, chcę, aby przejrzał kontroler widoku funkcji "Otwórz w" i nie uzyskaj dialogu "Poczta" od Apple, zasadniczo go pomijając. Dlatego próbuję udostępnić zdjęcie między rozszerzeniem udziału a moją aplikacją, tworząc grupę aplikacji, która jest udostępniana między aplikacją i wtyczką, a następnie przekazując ścieżki plików do otwartego adresu użytkownika delegata aplikacji z mojej aplikacji.Kod udostępniania ścieżki/pliku pliku między rozszerzeniem udziałów a aplikacją na iOS

Więc moim głównym delegatem aplikacji mam

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

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

które w zasadzie używać do sprawdzania za każdym razem, gdy mam pliku ścieżkę URL, który musi otworzyć inny przepływ.

W moim SHAREEXTENSION mam

#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 

A moja Info.plist dla rozszerzenia jest

<?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> 

mam w zasadzie stosować jakiś kod Commons License z internetu (podobno miejscu), który twierdzi, przeszedł proces sprawdzania sklepu z aplikacjami.

W kodzie są dwa obejścia, jeden to wywołanie OpenURL z rozszerzenia udziałów (co wydaje się, że od wyszukania SO nie jest normalnie możliwe bez obejścia w systemie iOS 8.3 i nowszych wersjach), a drugie to ukrycie tego wpisu dialog i klawiaturę, którą jabłko zapewnia domyślnie, gdy ktoś kliknie w udział. To działa.

Mam dwa pytania

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:

Więc szorowania poprzez dokumentacji znalazłem poprawkę dla pytania 2 i zmienił to. Teraz wszystko działa, i nie ma żadnego TRUEPREDICATE, czy zostanie to zaakceptowane w sklepie, czy jest inny sposób na zrobienie tego?

UPDATE 2:

Mam teraz używany NSUserDefaults przekazać dane z rozszerzenia aplikacji, przypuszczam, że to również jeden wymóg udostępniania danych.

+0

Zaktualizuję go nieco. Tak, został zaakceptowany w recenzji – Slartibartfast

+0

Dzięki za ten pomocny post, spędziłem kilka godzin szukając informacji na ten temat –

Odpowiedz

7

UPDATE

Aplikacja została przyjęta w przeglądzie z wykorzystaniem NSUSERDEFAULTS jako mechanizm przekazywania wiadomości. Oto kroki.

1.) Udział rozszerzenie:

#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. W metodzie OpenURL swojego delegata aplikacji

     //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 
        } 
    

Dzięki EasyNativeExtensions dla wskaźników

+0

Wielkie dzięki. To naprawdę mi pomaga. –

0

Twoje pytanie jest trochę pomieszane, ale jeśli chodzi o przekazywanie danych z jednej aplikacji do innej aplikacji masz świetne rozwiązanie dla tego, co jest UIPasteboard

Jeśli nie ma żadnego problemu dla skoków między aplikacjami używając niestandardowych procedur obsługi adresów URL, masz jeszcze 2 kroki.

Krok 1
W swojej pierwszej aplikacji, która jest odpowiedzialna za przekazywanie danych wdrożyć te metody, a następnie wywołać niestandardowy adres URL.

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


Krok 2
W swojej docelowej widoku kontrolera proste wezwanie UIPasteboard ponownie i uzyskać z niego dane.

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


Należy pamiętać, że mijamy UIImage i masz go w tym samym typie

+0

Nie sądzę, że rozumiesz intencję pytania. Chęć tutaj polega na umożliwieniu dowolnej aplikacji, która może korzystać z UiActivityViewController, aby móc udostępniać dane aplikacji OP, a nie tylko innej aplikacji napisanej przez OP. –

+0

@RocketGarden nie możesz tego zrobić. Nie wszystkie aplikacje akceptują udostępnianie, a kto akceptuje niektóre z nich, akceptuje linki, niektóre z nich zawierają numery niektórych z nich. To jest pomieszane. Jedyną opcją jest użycie http://stackoverflow.com/questions/13498459/how-to-display-the-default-ios-6-share-action-sheet- with-available-share-options –

+0

Ale OP chciał jego wniosek o przyjęcie akcji, a nie odwrotnie. Określi reguły określające dane, które zaakceptuje, a następnie każda aplikacja prezentująca UIActivityViewController będzie mogła przedstawić swoją aplikację jako cel dla udziału, jeśli wybrany zostanie właściwy typ danych. –

0
  1. Jeśli nie chcesz domyślny pokaz dialogowe z jabłkiem. Powinny być dziedziczone z UIViewController nie @interface ShareViewController: SLComposeServiceViewController
  2. W dokumencie dla programistów Apple nie zezwalaj na otwarcie aplikacji rozszerzeń bezpośrednio z aplikacją zawierającą aplikację z wyjątkiem aplikacji rozszerzającej Dzisiaj.
+0

Istnieją teraz przykłady, w których inne aplikacje używają rozszerzeń akcji w ten sposób. Na przykład Pixelmator używa akcji akcji do udostępniania obrazu za pomocą Snapseed, akcja akcji nie miała interfejsu użytkownika, a openURL jest wywoływana w extensionContext, aby wpłynąć na udział w sposób podobny do pokazanego dla rozszerzenia Today.Inny przykład ze Zdjęć można udostępnić wiele zdjęć do programu iBooks jako plik pdf. Jeśli wybierzesz kilka obrazów, nie pojawi się żaden interfejs użytkownika, jeśli istnieje wiele małych kontrolerów UIViewprzed wyświetleniem paska postępu, to zostanie otwarty program iBooks z plikiem PDF wyświetlanym w bibliotece. –

Powiązane problemy