# 有關(guān)Share Extension 【iOS擴(kuò)展開(kāi)發(fā)攻略】

最近接到的share Extension需求,要求做原生的share Extension,就去百度很多資料,坑坑洼洼的完成了任務(wù)上線,現(xiàn)在回過(guò)來(lái)總結(jié)一下踩過(guò)的坑.

  • 1、首先給大家介紹一下iOS擴(kuò)展.

    擴(kuò)展( Extension )是 iOS 8 中引入的一個(gè)非常重要的新特性。擴(kuò)展讓 app 之間的數(shù)據(jù)交互成為可能。用戶可以在 app 中使用其他應(yīng)用提供的功能,而無(wú)需離開(kāi)當(dāng)前的應(yīng)用。在 iOS 8 系統(tǒng)之前,每一個(gè) app 在物理上都是彼此獨(dú)立的, app 之間不能互訪彼此的私有數(shù)據(jù)。而在引入擴(kuò)展之后,其他 app 可以與擴(kuò)展進(jìn)行數(shù)據(jù)交換。基于安全和性能的考慮,每一個(gè)擴(kuò)展運(yùn)行在一個(gè)單獨(dú)的進(jìn)程中,它擁有自己的 bundle , bundle 后綴名是.appex 。擴(kuò)展 bundle 必須包含在一個(gè)普通應(yīng)用的 bundle 的內(nèi)部。

    iOS 8 系統(tǒng)有 6 個(gè)支持?jǐn)U展的系統(tǒng)區(qū)域,分別是 Today 、 Share 、 Action 、 Photo Editing 、 Storage Provider 、 Custom keyboard 。支持?jǐn)U展的系統(tǒng)區(qū)域也被稱為擴(kuò)展點(diǎn)。
    接下來(lái)我們主要講一下share Extension具體實(shí)現(xiàn)。

  • 2、代碼實(shí)現(xiàn)

    1、 share Extension實(shí)現(xiàn)是需要依賴一個(gè)工程,如果你沒(méi)有工程需要重新創(chuàng)建一個(gè)(如果你不會(huì)創(chuàng)建工程,你可以關(guān)閉瀏覽器了);創(chuàng)建完工程后我們需要?jiǎng)?chuàng)建一個(gè)share Extension的工程,創(chuàng)建方法如圖:

image scr

接著繼續(xù)點(diǎn)擊


image

image
image

完成后會(huì)如最后一張圖片,現(xiàn)在我們就可以嘗試一下share的功能。

image

share Extension項(xiàng)目運(yùn)行必須有一個(gè)容器,我們先用默認(rèn)的瀏覽器嘗試一下,
image

進(jìn)入手機(jī)頁(yè)面你會(huì)發(fā)現(xiàn)share Extension按鈕是灰色狀態(tài)不能點(diǎn)擊,我先需要打開(kāi)一個(gè)網(wǎng)頁(yè),例如:(www.baidu.com),現(xiàn)在我們點(diǎn)擊方向按鈕,會(huì)在share Extension欄找到我們的APP

image

如果沒(méi)有發(fā)現(xiàn)APP,點(diǎn)擊旁邊更多按鈕,進(jìn)入下級(jí)頁(yè)面,把APP權(quán)限按鈕打開(kāi)


image

2、接下來(lái)我們準(zhǔn)備處理share Extension數(shù)據(jù)

image

圖片為蘋果原生為我們提供的share Extension頁(yè)面,進(jìn)入程序shareViewController頁(yè)面


image
2.1依次來(lái)解析一下這三個(gè)方法
/*
isContentValid來(lái)判斷我們獲取到得數(shù)據(jù)是否是我們想要的。
*/
- (BOOL)isContentValid {
// Do validation of contentText and/or NSExtensionContext attachments here
return YES;
} 
/**
*  點(diǎn)擊取消按鈕
*/
- (void)didSelectCancel
{
[super didSelectCancel];
}

/**
 *  點(diǎn)擊提交按鈕
 */
- (void)didSelectPost
{
    // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.

    // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
    [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
    }

在這兩個(gè)方法里面可以進(jìn)行一些自定義的操作。一般情況下,當(dāng)用戶點(diǎn)擊提交按鈕的時(shí)候,擴(kuò)展要做的事情就是要把數(shù)據(jù)取出來(lái),并且放入一個(gè)與Containing App(** 容器程序,盡管蘋果開(kāi)放了Extension,但是在iOS中extension并不能單獨(dú)存在,要想提交到AppStore,必須將Extension包含在一個(gè)App中提交,并且App的實(shí)現(xiàn)部分不能為空,這個(gè)包含Extension的App就叫Containing app。Extension會(huì)隨著Containing App的安裝而安裝,同時(shí)隨著ContainingApp的卸載而卸載。**)共享的數(shù)據(jù)介質(zhì)中(包括NSUserDefault、Sqlite、CoreData),要跟容器程序進(jìn)行數(shù)據(jù)交互需要借助AppGroups服務(wù),下面的章節(jié)會(huì)對(duì)這塊進(jìn)行詳細(xì)說(shuō)明。下面先來(lái)看看怎么獲取擴(kuò)展中的數(shù)據(jù)。

2.2在ShareExtension中,UIViewController包含一個(gè)extensionContext這樣的上下文對(duì)象:
@interface UIViewController(NSExtensionAdditions) <NSExtensionRequestHandling>

// Returns the extension context. Also acts as a convenience method for a view controller to check if it participating in an extension request.
@property (nullable, nonatomic,readonly,strong) NSExtensionContext *extensionContext NS_AVAILABLE_IOS(8_0);
@end

通過(guò)操作它就可以獲取到share Extension的數(shù)據(jù),返回宿主應(yīng)用的界面等操作。我們可以先看一下extensionContext的定義。

NS_CLASS_AVAILABLE(10_10, 8_0)
@interface NSExtensionContext : NSObject

// The list of input NSExtensionItems associated with the context. If the context has no input items, this array will be empty.
@property(readonly, copy, NS_NONATOMIC_IOSONLY) NSArray *inputItems;

// Signals the host to complete the app extension request with the supplied result items. The completion handler optionally contains any work which the extension may need to perform after the request has been completed, as a background-priority task. The `expired` parameter will be YES if the system decides to prematurely terminate a previous non-expiration invocation of the completionHandler. Note: calling this method will eventually dismiss the associated view controller.
- (void)completeRequestReturningItems:(nullable NSArray *)items completionHandler:(void(^ __nullable)(BOOL expired))completionHandler;

// Signals the host to cancel the app extension request, with the supplied error, which should be non-nil. The userInfo of the NSError will contain a key NSExtensionItemsAndErrorsKey which will have as its value a dictionary of NSExtensionItems and associated NSError instances.
- (void)cancelRequestWithError:(NSError *)error;

// Asks the host to open an URL on the extension's behalf
- (void)openURL:(NSURL *)URL completionHandler:(void (^ __nullable)(BOOL success))completionHandler;

@end

// Key in userInfo. Value is a dictionary of NSExtensionItems and associated NSError instances.
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionItemsAndErrorsKey NS_AVAILABLE(10_10, 8_0);

// The host process will enter the foreground
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionHostWillEnterForegroundNotification NS_AVAILABLE_IOS(8_2);

// The host process did enter the background
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionHostDidEnterBackgroundNotification NS_AVAILABLE_IOS(8_2);
2.3NSExtensionContext的結(jié)構(gòu)比較簡(jiǎn)單,包含一個(gè)屬性和三個(gè)方法。其說(shuō)明如下:
方法 說(shuō)明
inputItems 該數(shù)組存儲(chǔ)著容器應(yīng)用傳入給NSExtensionContext的NSExtensionItem數(shù)組。其中每個(gè)NSExtensionItem標(biāo)識(shí)了一種類型的數(shù)據(jù)。要獲取數(shù)據(jù)就要從這個(gè)屬性入手。
completeRequestReturningItems:<br />completionHandler: 通知宿主程序的擴(kuò)展已完成請(qǐng)求。調(diào)用此方法后,擴(kuò)展UI會(huì)關(guān)閉并返回容器程序中。其中的items就是返回宿主程序的數(shù)據(jù)項(xiàng)。
cancelRequestWithError: 通知宿主程序的擴(kuò)展已取消請(qǐng)求。調(diào)用此方法后,擴(kuò)展UI會(huì)關(guān)閉并返回容器程序中。其中error為錯(cuò)誤的描述信息。
NSExtensionItemsAndErrorsKey NSExtensionItem的userInfo屬性中對(duì)應(yīng)的錯(cuò)誤信息鍵名。
2.4類的下面還定義了一些通知,這些通知都是跟宿主程序的行為相關(guān),在設(shè)計(jì)擴(kuò)展的時(shí)候可以根據(jù)這些通知來(lái)進(jìn)行對(duì)應(yīng)的操作。其說(shuō)明如下:
通知名稱 說(shuō)明
NSExtensionHostWillEnterForegroundNotification 宿主程序?qū)⒁祷厍芭_(tái)通知NSExtensionHostDidEnterBackgroundNotification 宿主程序進(jìn)入后臺(tái)通知
NSExtensionHostWillResignActiveNotification 宿主程序?qū)⒁粧炱鹜ㄖ?/td>
NSExtensionHostDidBecomeActiveNotification 宿主程序被激活通知
2.5從inputItems中獲取數(shù)據(jù)

inputItems是包含NSExtensionItem類型對(duì)象的數(shù)組。那么,要處理里面的數(shù)據(jù)還得先來(lái)了解一下NSExtensionItem的結(jié)構(gòu):

@interface NSExtensionItem : NSObject<NSCopying, NSSecureCoding>

// (optional) title for the item
@property(nullable, copy, NS_NONATOMIC_IOSONLY) NSAttributedString *attributedTitle;

// (optional) content text
@property(nullable, copy, NS_NONATOMIC_IOSONLY) NSAttributedString *attributedContentText;

// (optional) Contains images, videos, URLs, etc. This is not meant to be an array of alternate data formats/types, but instead a collection to include in a social media post for example. These items are always typed NSItemProvider.
@property(nullable, copy, NS_NONATOMIC_IOSONLY) NSArray *attachments;

// (optional) dictionary of key-value data. The key/value pairs accepted by the service are expected to be specified in the extension's Info.plist. The values of NSExtensionItem's properties will be reflected into the dictionary.
@property(nullable, copy, NS_NONATOMIC_IOSONLY) NSDictionary *userInfo;

@end

// Keys corresponding to properties exposed on the NSExtensionItem interface
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionItemAttributedTitleKey NS_AVAILABLE(10_10, 8_0);
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionItemAttributedContentTextKey NS_AVAILABLE(10_10, 8_0);
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionItemAttachmentsKey NS_AVAILABLE(10_10, 8_0);

NSExtensionItem包含四個(gè)屬性

屬性 說(shuō)明
attributedTitle 標(biāo)題
attributedContentText 內(nèi)容。
attachments 附件數(shù)組,包含圖片、視頻、鏈接等資源,封裝在NSItemProvider類型中。
userInfo 一個(gè)key-value結(jié)構(gòu)的數(shù)據(jù)。NSExtensionItem中的屬性都會(huì)在這個(gè)屬性中一一映射。

對(duì)應(yīng)userInfo結(jié)構(gòu)中的NSExtensionItem屬性的鍵名如下:

名稱 說(shuō)明
NSExtensionItemAttributedTitleKey 標(biāo)題的鍵名
NSExtensionItemAttributedContentTextKey 內(nèi)容的鍵名。
NSExtensionItemAttachmentsKey 附件的鍵名

從上面的定義可以看出除了文本內(nèi)容,其他類型的內(nèi)容都是作為附件存儲(chǔ)的,而附件又是封裝在一個(gè)叫NSItemProvider的類型中,其定義如下:

typedef void (^NSItemProviderCompletionHandler)(__nullable id <NSSecureCoding> item, NSError * __null_unspecified error);
typedef void (^NSItemProviderLoadHandler)(__null_unspecified NSItemProviderCompletionHandler completionHandler, __null_unspecified Class expectedValueClass, NSDictionary * __null_unspecified options);

// An NSItemProvider is a high level abstraction for file-like data objects supporting multiple representations and preview images.
NS_CLASS_AVAILABLE(10_10, 8_0)
@interface NSItemProvider : NSObject <NSCopying>

// Initialize an NSItemProvider with a single handler for the given item.
- (instancetype)initWithItem:(nullable id <NSSecureCoding>)item typeIdentifier:(nullable NSString *)typeIdentifier NS_DESIGNATED_INITIALIZER;

// Initialize an NSItemProvider with load handlers for the given file URL, and the file content.
- (nullable instancetype)initWithContentsOfURL:(null_unspecified NSURL *)fileURL;

// Sets a load handler block for a specific type identifier. Handlers are invoked on demand through loadItemForTypeIdentifier:options:completionHandler:. To complete loading, the implementation has to call the given completionHandler. Both expectedValueClass and options parameters are derived from the completionHandler block.
- (void)registerItemForTypeIdentifier:(NSString *)typeIdentifier loadHandler:(NSItemProviderLoadHandler)loadHandler;

// Returns the list of registered type identifiers
@property(copy, readonly, NS_NONATOMIC_IOSONLY) NSArray *registeredTypeIdentifiers;

// Returns YES if the item provider has at least one item that conforms to the supplied type identifier.
- (BOOL)hasItemConformingToTypeIdentifier:(NSString *)typeIdentifier;

// Loads the best matching item for a type identifier. The client's expected value class is automatically derived from the blocks item parameter. Returns an error if the returned item class does not match the expected value class. Item providers will perform simple type coercions (eg. NSURL to NSData, NSURL to NSFileWrapper, NSData to UIImage).
- (void)loadItemForTypeIdentifier:(NSString *)typeIdentifier options:(nullable NSDictionary *)options completionHandler:(nullable NSItemProviderCompletionHandler)completionHandler;

@end

// Common keys for the item provider options dictionary.
FOUNDATION_EXTERN NSString * __null_unspecified const NSItemProviderPreferredImageSizeKey NS_AVAILABLE(10_10, 8_0); // NSValue of CGSize or NSSize, specifies image size in pixels.

@interface NSItemProvider(NSPreviewSupport)

// Sets a custom preview image handler block for this item provider. The returned item should preferably be NSData or a file NSURL.
@property(nullable, copy, NS_NONATOMIC_IOSONLY) NSItemProviderLoadHandler previewImageHandler NS_AVAILABLE(10_10, 8_0);

// Loads the preview image for this item by either calling the supplied preview block or falling back to a QuickLook-based handler. This method, like loadItemForTypeIdentifier:options:completionHandler:, supports implicit type coercion for the item parameter of the completion block. Allowed value classes are: NSData, NSURL, UIImage/NSImage.
- (void)loadPreviewImageWithOptions:(null_unspecified NSDictionary *)options completionHandler:(null_unspecified NSItemProviderCompletionHandler)completionHandler NS_AVAILABLE(10_10, 8_0);

@end

// Keys used in property list items received from or sent to JavaScript code

// If JavaScript code passes an object to its completionFunction, it will be placed into an item of type kUTTypePropertyList, containing an NSDictionary, under this key.
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionJavaScriptPreprocessingResultsKey NS_AVAILABLE(10_10, 8_0);

// Arguments to be passed to a JavaScript finalize method should be placed in an item of type kUTTypePropertyList, containing an NSDictionary, under this key.
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionJavaScriptFinalizeArgumentKey NS_AVAILABLE_IOS(8_0);

// Errors

// Constant used by NSError to distinguish errors belonging to the NSItemProvider domain
FOUNDATION_EXTERN NSString * __null_unspecified const NSItemProviderErrorDomain NS_AVAILABLE(10_10, 8_0);

// NSItemProvider-related error codes
typedef NS_ENUM(NSInteger, NSItemProviderErrorCode) {
    NSItemProviderUnknownError                                      = -1,
    NSItemProviderItemUnavailableError                              = -1000,
    NSItemProviderUnexpectedValueClassError                         = -1100,
    NSItemProviderUnavailableCoercionError NS_AVAILABLE(10_11, 9_0) = -1200
} NS_ENUM_AVAILABLE(10_10, 8_0);

NSItemProvider結(jié)構(gòu)說(shuō)明

名稱 說(shuō)明
initWithItem:typeIdentifier: 初始化方法,item為附件的數(shù)據(jù),typeIdentifier是附件對(duì)應(yīng)的類型標(biāo)識(shí),對(duì)應(yīng)UTI的描述。
initWithContentsOfURL 根據(jù)制定的文件路徑來(lái)初始化。
registerItemForTypeIdentifier:loadHandler: 為一種資源類型自定義加載過(guò)程。這個(gè)方法主要針對(duì)自定義資源使用,例如自己定義的類或者文件格式等。當(dāng)調(diào)用loadItemForTypeIdentifier:options:completionHandler:方法時(shí)就會(huì)觸發(fā)定義的加載過(guò)程。
hasItemConformingToTypeIdentifier: 用于判斷是否有typeIdentifier(UTI)所指定的資源存在。存在則返回YES,否則返回NO。<br />該方法結(jié)合loadItemForTypeIdentifier:options:completionHandler:使用。
loadItemForTypeIdentifier:options:completionHandler: 加載typeIdentifier指定的資源。加載是一個(gè)異步過(guò)程,加載完成后會(huì)觸發(fā)completionHandler。
loadPreviewImageWithOptions:completionHandler: 加載資源的預(yù)覽圖片。

由此可見(jiàn),其結(jié)構(gòu)如下圖所示:

image

為了要取到宿主程序提供的數(shù)組,那么只要關(guān)注loadItemTypeIdentifier:options:completionHandler方法的使用即可。有了上面的了解,那么接下來(lái)就是對(duì)inputItems進(jìn)行數(shù)據(jù)分析并提取了,這里以一個(gè)鏈接的share Extension為例,改寫視圖控制器中的didSelectPost方法??聪旅娴拇a:

    - (void)didSelectPost
{
    __block BOOL hasExistsUrl = NO;
    [self.extensionContext.inputItems enumerateObjectsUsingBlock:^(NSExtensionItem * _Nonnull extItem, NSUInteger idx, BOOL * _Nonnull stop) {
        [item.attachments enumerateObjectsUsingBlock:^(NSItemProvider * _Nonnull itemProvider, NSUInteger idx, BOOL * _Nonnull stop) {
         //獲取圖片
            if ([itemProvider hasItemConformingToTypeIdentifier:@"public.url"])
                        {
                            [itemProvider loadItemForTypeIdentifier:@"public.url"
                                                            options:nil
                                                  completionHandler:^(id<NSSecureCoding>  _Nullable item, NSError * _Null_unspecified error) {
            
                                                      if ([(NSObject *)item isKindOfClass:[NSURL class]])
                                                      {
                                                          NSLog(@"share Extension的URL = %@", item);
                                                      }
            
                                                  }];
            
                            hasExistsUrl = YES;
                            *stop = YES;
                        }
            
                    }];
          //獲取鏈接
            if ([itemProvider hasItemConformingToTypeIdentifier:@"public.url"])
            {
                [itemProvider loadItemForTypeIdentifier:@"public.url"
                                                options:nil
                                      completionHandler:^(id<NSSecureCoding>  _Nullable item, NSError * _Null_unspecified error) {

                                          if ([(NSObject *)item isKindOfClass:[NSURL class]])
                                          {
                                              NSLog(@"share Extension的URL = %@", item);
                                          }

                                      }];

                hasExistsUrl = YES;
                *stop = YES;
            }

        }];

        if (hasExistsUrl)
        {
            *stop = YES;
        }

    }];

    // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
    // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
//    [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
}

上面的例子中遍歷了extensionContext的inputItems數(shù)組中所有NSExtensionItem對(duì)象,然后從這些對(duì)象中遍歷attachments數(shù)組中的所有NSItemProvider對(duì)象。匹配第一個(gè)包含public.url標(biāo)識(shí)的附件(具體要匹配什么資源,數(shù)量是多少皆有自己的業(yè)務(wù)所決定)。**** 注意:在上面代碼中注釋了[self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];這行代碼,主要是使到視圖控制器不被關(guān)閉,等到實(shí)現(xiàn)相應(yīng)的處理后再進(jìn)行調(diào)用該方法,對(duì)share Extension視圖進(jìn)行關(guān)閉。**** 在下面的章節(jié)會(huì)說(shuō)明這一點(diǎn)。

2.5 將share Extension數(shù)據(jù)傳遞給容器程序

上面章節(jié)已經(jīng)講述了如何取得宿主應(yīng)用所share Extension的內(nèi)容。那么,接下來(lái)就是將這些內(nèi)容傳遞給容器程序進(jìn)行相應(yīng)的操作(如:在一款社交應(yīng)用中,可能會(huì)為取得的share Extension內(nèi)容發(fā)布一條用戶動(dòng)態(tài))。在默認(rèn)情況下,iOS的應(yīng)用是存在一個(gè)沙盒里面的,不允許應(yīng)用與應(yīng)用直接進(jìn)行數(shù)據(jù)的交互。為此,蘋果提供了一項(xiàng)叫App Groups的服務(wù),該服務(wù)允許開(kāi)發(fā)者可以在自己的應(yīng)用之間通過(guò)NSUserDefaults、NSFileManager或者CoreData來(lái)進(jìn)行相互的數(shù)據(jù)傳輸。下面介紹如何激活A(yù)pp Groups服務(wù):

首先要有一個(gè)獨(dú)立的AppID(帶通配符*號(hào)的AppID是不允許激活A(yù)pp Groups的)

==Xcode中直接打開(kāi)group,設(shè)置group后,開(kāi)發(fā)者網(wǎng)站會(huì)同步**==

image

點(diǎn)擊添加按鈕,會(huì)出現(xiàn)添加框

[圖片上傳失敗...(image-bf122-1526268288844)]

gronp.后面填寫你項(xiàng)目的bundle identifer 即可;同樣在share 項(xiàng)目中添加group信息,(系統(tǒng)應(yīng)該已經(jīng)默認(rèn)為你添加上,默認(rèn)選擇就好)

image

至此,應(yīng)用和擴(kuò)展的App Groups服務(wù)都已經(jīng)啟動(dòng),現(xiàn)在就要進(jìn)行share Extension內(nèi)容的傳輸操作。下面分別介紹一下NSUserDefaults、NSFileManager以及CoreData三種方式是如何實(shí)現(xiàn)App Groups下的數(shù)據(jù)操作:

  • NSUserDefaults:要想設(shè)置或訪問(wèn)Group的數(shù)據(jù),不能在使用standardUserDefaults方法來(lái)獲取一個(gè)NSUserDefaults對(duì)象了。應(yīng)該使用initWithSuiteName:方法來(lái)初始化一個(gè)NSUserDefaults對(duì)象,其中的SuiteName就是創(chuàng)建的Group的名字,然后利用這個(gè)對(duì)象來(lái)實(shí)現(xiàn),跨應(yīng)用的數(shù)據(jù)讀寫,代碼如下:

//初始化一個(gè)供App Groups使用的NSUserDefaults對(duì)象

NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.Taiyi.shareP"];

//寫入數(shù)據(jù)

[userDefaults setValue:@"value" forKey:@"key"];

//讀取數(shù)據(jù)

NSLog(@"%@", [userDefaults valueForKey:@"key"]);
  • NSFileManager:通過(guò)調(diào)用 containerURLForSecurityApplicationGroupIdentifier:方法可以獲得AppGroup的共享目錄,然后在此目錄的基礎(chǔ)上實(shí)現(xiàn)任意的文件操作。代碼如下:

    //獲取分組的共享目錄
    
    NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.cn.vimfung.ShareExtensionDemo"];
    
    NSURL *fileURL = [groupURL URLByAppendingPathComponent:@"demo.txt"];
    
    //寫入文件
    
    [@"abc" writeToURL:fileURL atomically:YES encoding:NSUTF8StringEncoding error:nil];
    
    //讀取文件
    
    NSString *str = [NSString stringWithContentsOfURL:fileURL encoding:NSUTF8StringEncoding error:nil];
    
    NSLog(@"str = %@", str);
    
  • CoreData:其實(shí)CoreData是基于NSFileManager取得共享目錄后來(lái)實(shí)現(xiàn)數(shù)據(jù)共享的。即在初始化CoreData時(shí),先使用NSFileManager取得共享目錄,然后再指定共享目錄為存儲(chǔ)數(shù)據(jù)文件的目錄(如存儲(chǔ)的sqlite文件)。代碼如下:

         //獲取分組的共享項(xiàng)目
      NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.cn.vimfung.ShareExtensionDemo"];
      NSURL *storeURL = [containerURL URLByAppendingPathComponent:@"DataModel.sqlite"];
      
      //初始化持久化存儲(chǔ)調(diào)度器
      NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"DataModel" withExtension:@"momd"];
      
      NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
      NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
      
      [coordinator addPersistentStoreWithType:NSSQLiteStoreType
                                configuration:nil
                                          URL:storeURL
                                      options:nil
                                        error:nil];
      
      //創(chuàng)建受控對(duì)象上下文
      NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
      
      [context performBlockAndWait:^{
          [context setPersistentStoreCoordinator:coordinator];
    

    }];
    為了方便演示,這里會(huì)使用NSUserDefault來(lái)直接把取到的url地址保存起來(lái)。代碼如下所示:

_(默認(rèn)情況下,如果用戶點(diǎn)擊Post按鈕后,share Extension界面就會(huì)消失,用戶可以繼續(xù)對(duì)宿主程序進(jìn)行操作。這些都要靠NSExtensionContextdcompleteRequestReturningItems:completionHandler:方法來(lái)實(shí)現(xiàn)?,F(xiàn)在,由于在didSelectPost方法中加入了share Extension內(nèi)容的處理,由于獲取附件是一個(gè)異步過(guò)程,那么,就需要做好界面上的提示。否則,share Extension界面消失后由于沒(méi)有操作提示,會(huì)使用戶誤以為界面進(jìn)行卡死的狀態(tài),其實(shí)是share Extension內(nèi)容還沒(méi)有處理完成。接下來(lái)就是優(yōu)化UI上的提示操作,
)_

- (void)didSelectPost {
    
    _viewCon = [ViewController new];
    NSString *aa = [_viewCon setNumber];
    NSLog(@"%@",aa);
    //加載動(dòng)畫初始化
    UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
    activityIndicatorView.frame = CGRectMake((self.view.frame.size.width - activityIndicatorView.frame.size.width) / 2,
                                             (self.view.frame.size.height - activityIndicatorView.frame.size.height) / 2,
                                             activityIndicatorView.frame.size.width,
                                             activityIndicatorView.frame.size.height);
    activityIndicatorView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
    [self.view addSubview:activityIndicatorView];
    
    //激活加載動(dòng)畫
    [activityIndicatorView startAnimating];

    // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
    [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
    
    __block BOOL hasExistsUrl = NO;
    [self.extensionContext.inputItems enumerateObjectsUsingBlock:^(NSExtensionItem * _Nonnull extItem, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"%@-----------%@",extItem.attributedTitle,extItem.attributedContentText);
    
    NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.taiyi.shareP"];
    NSAttributedString *strings = [extItem.attributedContentText attributedSubstringFromRange:NSMakeRange(0, extItem.attributedContentText.length)];
    NSArray *array = [strings.string componentsSeparatedByString:@"\n"];
    NSString *firstString = array[0];
    NSLog(@"%@",firstString);
    [userDefaults setValue:firstString forKey:@"share-content"];
   
    [extItem.attachments enumerateObjectsUsingBlock:^(NSItemProvider * _Nonnull itemProvider, NSUInteger idx, BOOL * _Nonnull stop) {

        NSLog(@"%d",[itemProvider hasItemConformingToTypeIdentifier:@"public.url"]);
        
        if ([itemProvider hasItemConformingToTypeIdentifier:@"public.text"])
        {
            //加載typeIdentifier指定的資源
            [itemProvider loadItemForTypeIdentifier:@"public.text"
                                            options:nil
                                  completionHandler:^(id<NSSecureCoding>  _Nullable item, NSError * _Null_unspecified error) {
                                      
                                      if ([(NSObject *)item isKindOfClass:[NSURL class]])
                                          
                                      {
                                          NSLog(@"share Extension的URL = %@", item);
                                          
                                          [userDefaults setValue:((NSURL *)item).absoluteString forKey:@"share-text-url"];
                                          
                                          //用于標(biāo)記是新的share Extension
                                          [userDefaults setBool:YES forKey:@"has-new-share"];
                                          
                                          [activityIndicatorView stopAnimating];
                                          [self.extensionContext completeRequestReturningItems:@[extItem] completionHandler:nil];
                                          
                                      }
                                      
                                  }];
            
            hasExistsUrl = YES;
            *stop = YES;
        }
        
        if ([itemProvider hasItemConformingToTypeIdentifier:@"public.image"])
        {
            //加載typeIdentifier指定的資源
            [itemProvider loadItemForTypeIdentifier:@"public.image"
                                            options:nil
                                  completionHandler:^(id<NSSecureCoding>  _Nullable item, NSError * _Null_unspecified error) {
                                      
                                      if ([(NSObject *)item isKindOfClass:[NSURL class]])
                                          
                                      {
                                          NSLog(@"share Extension的URL = %@", item);
                                          
                                          [userDefaults setValue:((NSURL *)item).absoluteString forKey:@"share-image-url"];
                                          
                                          //用于標(biāo)記是新的share Extension
                                          [userDefaults setBool:YES forKey:@"has-new-share"];
                                          
                                          [activityIndicatorView stopAnimating];
                                          [self.extensionContext completeRequestReturningItems:@[extItem] completionHandler:nil];
                                          
                                      }
                                      
                                  }];
            
            hasExistsUrl = YES;
            *stop = YES;
        }

        
        
        if ([itemProvider hasItemConformingToTypeIdentifier:@"public.url"])
        {
            //加載typeIdentifier指定的資源
            [itemProvider loadItemForTypeIdentifier:@"public.url"
                                            options:nil
                                  completionHandler:^(id<NSSecureCoding>  _Nullable item, NSError * _Null_unspecified error) {
                                      
                                      if ([(NSObject *)item isKindOfClass:[NSURL class]])

                                      {
                                          NSLog(@"share Extension的URL = %@", item);
                                          
                                          [userDefaults setValue:((NSURL *)item).absoluteString forKey:@"share-url"];
                                         
                                          //用于標(biāo)記是新的share Extension
                                          [userDefaults setBool:YES forKey:@"has-new-share"];
                                          
                                          [activityIndicatorView stopAnimating];
                                          [self.extensionContext completeRequestReturningItems:@[extItem] completionHandler:nil];
                                          
                                      }
                                      
                                  }];
            
            hasExistsUrl = YES;
            *stop = YES;
        }
        
    }];
    
    if (hasExistsUrl)
    {
        *stop = YES;
    }
    
}];

if (!hasExistsUrl)
{
    //直接退出
    [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
}
}

2.6 容器程序獲取share Extension數(shù)據(jù)

插件的工作基本上已經(jīng)全部開(kāi)發(fā)完成了,接下來(lái)就是容器程序獲取數(shù)據(jù)并進(jìn)行操作。下面是容器程序的處理代碼:

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    //獲取共享的UserDefaults
    NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.cn.vimfung.ShareExtensionDemo"];
    if ([userDefaults boolForKey:@"has-new-share"])
    {
        NSLog(@"新的share Extension : %@", [userDefaults valueForKey:@"share-url"]);

        //重置share Extension標(biāo)識(shí)
        [userDefaults setBool:NO forKey:@"has-new-share"];
    }
}

為了方便演示,這里直接在AppDelegate中的applicationDidBecomeActive:方法中檢測(cè)是否有新的share Extension,如果有則通過(guò)Log打印鏈接出來(lái)。

2.7 在share share Extension中,應(yīng)該會(huì)有很多同學(xué)因?yàn)閿?shù)據(jù)傳輸,頁(yè)面調(diào)用問(wèn)題發(fā)愁,接下來(lái)重點(diǎn)介紹一下我使用的方法==直接喚起APP,完成 share Extension==

  • 我們需要給APP配置一個(gè)url Schemes;
image
  • 然后在shareViewController里面調(diào)用

      __block BOOL hasExistsUrl = NO;
      [self.extensionContext.inputItems enumerateObjectsUsingBlock:^(NSExtensionItem * _Nonnull extItem, NSUInteger idx, BOOL * _Nonnull stop) {
          NSLog(@"%@-----------%@",extItem.attributedTitle,extItem.attributedContentText);
          NSAttributedString *strings = [extItem.attributedContentText attributedSubstringFromRange:NSMakeRange(0, extItem.attributedContentText.length)];
      
      NSArray *array = [strings.string componentsSeparatedByString:@"\n"];
      self.titleString = [NSString stringWithFormat:@"%@",array[0]];
      
      [extItem.attachments enumerateObjectsUsingBlock:^(NSItemProvider * _Nonnull itemProvider, NSUInteger idx, BOOL * _Nonnull stop) {
          //用于判斷是否有typeIdentifier(UTI)所指定的資源存在。
          if ([itemProvider hasItemConformingToTypeIdentifier:@"public.url"])
          {
              //加載typeIdentifier指定的資源
              [itemProvider loadItemForTypeIdentifier:@"public.url"
                                              options:nil
                                    completionHandler:^(id<NSSecureCoding>  _Nullable item, NSError * _Null_unspecified error) {
                                        
                                        if ([(NSObject *)item isKindOfClass:[NSURL class]])
                                        {
                                            NSLog(@"分享的URL = %@", item);
                                            self.urlString = [NSString stringWithFormat:@"%@",item];
    
    
                                            NSString *urlStr = [NSString stringWithFormat:@"shareP://?articleTitle=%@&articleUrl=%@",[self encode:self.titleString], [self encode:self.urlString]];
    
                                            if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:urlStr]]) {
                                                //可以調(diào)起APP
                                                [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlStr]];
                                                NSLog(@"調(diào)起成功");
                                                
                                                //直接退出
                                                [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
                                            }
                                            
                                        }
                                        
                                    }];
              
              hasExistsUrl = YES;
              *stop = YES;
          }
          
      }];
      
      if (hasExistsUrl)
      {
          *stop = YES;
      }
           }];
      
      if (!hasExistsUrl)
      {
          //直接退出
          [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
      }
      [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userHasebeenLocation) name:@"dismissController" object:nil];
    
  • 在項(xiàng)目appDelegate中接收信息

//授權(quán)登錄操作
    -(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
        NSString *urlStr = url.absoluteString;
        NSString *sechemes = url.scheme;
        if([[self getAppSchemeString] isEqualToString:sechemes]){
    
    if ([urlStr containsString:@"articleTitle"] && [urlStr containsString:@"articleUrl"]) {
        NSRange range1 = [urlStr rangeOfString:@"="];
        NSRange range2 = [urlStr rangeOfString:@"&"];
        NSString *articleTitle = [urlStr substringWithRange:NSMakeRange(range1.location + range1.length, range2.location - range1.location - range1.length)];
        
        NSString *stateStr = [urlStr substringFromIndex:range2.location+1];
        NSRange range3 = [stateStr rangeOfString:@"="];
        NSString *articleUrl = [stateStr substringFromIndex:range3.location+1];
        NSLog(@"%@====%@",articleTitle,articleUrl);
        
        

            //跳轉(zhuǎn)代碼就是跳轉(zhuǎn)到你自己設(shè)計(jì)的控制器,我這里就不寫了
    }
    
    }
    return YES;
    }

到此,share Extension 調(diào)用APP完成,剩下的全部都可以在APP內(nèi)部操作了,是不是很方便,

2.7配置info文件

group設(shè)置完成后,我們需要配置修改info文件中的NSExtensionActivationRule字段

image

我們只需要關(guān)注以下幾個(gè)字段的設(shè)置:

名稱 說(shuō)明
Bundle display name 擴(kuò)展的顯示名稱,默認(rèn)跟你的項(xiàng)目名稱相同,可以通過(guò)修改此字段來(lái)控制擴(kuò)展的顯示名稱。
NSExtension 擴(kuò)展描述字段,用于描述擴(kuò)展的屬性、設(shè)置等。作為一個(gè)擴(kuò)展項(xiàng)目必須要包含此字段。
NSExtensionAttributes 擴(kuò)展屬性集合字段。用于描述擴(kuò)展的屬性。
NSExtensionActivationRule 激活擴(kuò)展的規(guī)則。默認(rèn)為字符串“TRUEPREDICATE”,表示在share Extension菜單中一直顯示該擴(kuò)展??梢詫㈩愋透臑镈ictionary類型,然后添加以下字段:<br />NSExtensionActivationSupportsAttachmentsWithMaxCount<br />NSExtensionActivationSupportsAttachmentsWithMinCount<br />NSExtensionActivationSupportsImageWithMaxCount<br />NSExtensionActivationSupportsMovieWithMaxCount<br />NSExtensionActivationSupportsWebPageWithMaxCount<br />NSExtensionActivationSupportsWebURLWithMaxCount
NSExtensionMainStoryboard 設(shè)置主界面的Storyboard,如果不想使用storyboard,也可以使用NSExtensionPrincipalClass指定自定義UIViewController子類名
NSExtensionPointIdentifier 擴(kuò)展標(biāo)識(shí),在share Extension擴(kuò)展中為:com.apple.share-services
NSExtensionPrincipalClass 自定義UI的類名
NSExtensionActivationSupportsAttachmentsWithMaxCount 附件最多限制,為數(shù)值類型。附件包括File、Image和Movie三大類,單一、混選總量不超過(guò)指定數(shù)量
NSExtensionActivationSupportsAttachmentsWithMinCount 附件最少限制,為數(shù)值類型。當(dāng)設(shè)置NSExtensionActivationSupportsAttachmentsWithMaxCount時(shí)生效,默認(rèn)至少選擇1個(gè)附件,share Extension菜單中才顯示擴(kuò)展插件圖標(biāo)。
NSExtensionActivationSupportsFileWithMaxCount 文件最多限制,為數(shù)值類型。文件泛指除Image/Movie之外的附件,例如【郵件】附件、【語(yǔ)音備忘錄】等。<br /><br />單一、混選均不超過(guò)指定數(shù)量。
NSExtensionActivationSupportsImageWithMaxCount 圖片最多限制,為數(shù)值類型。單一、混選均不超過(guò)指定數(shù)量
NSExtensionActivationSupportsMovieWithMaxCount 視頻最多限制,為數(shù)值類型。單一、混選均不超過(guò)指定數(shù)量。
NSExtensionActivationSupportsText 是否支持文本類型,布爾類型,默認(rèn)不支持。如【備忘錄】的share Extension
NSExtensionActivationSupportsWebURLWithMaxCount Web鏈接最多限制,為數(shù)值類型。默認(rèn)不支持share Extension超鏈接,需要自己設(shè)置一個(gè)數(shù)值。
NSExtensionActivationSupportsWebPageWithMaxCount Web頁(yè)面最多限制,為數(shù)值類型。默認(rèn)不支持Web頁(yè)面share Extension,需要自己設(shè)置一個(gè)數(shù)值。

對(duì)于不同的應(yīng)用里面有可能出現(xiàn)只允許接受某種類型的內(nèi)容,那么Share Extension就不能一直出現(xiàn)在share Extension菜單中,因?yàn)椴煌膽?yīng)用提供的share Extension內(nèi)容不一樣,這就需要通過(guò)設(shè)置NSExtensionActivationRule字段來(lái)決定Share Extension是否顯示。例如,只想接受其他應(yīng)用share Extension鏈接到自己的應(yīng)用,那么可以通過(guò)下面的步驟來(lái)設(shè)置:

將NSExtensionActivationRule字段類型由String改為Dictionary。

展開(kāi)NSExtensionActivationRule字段,創(chuàng)建其子項(xiàng)NSExtensionActivationSupportsWebURLWithMaxCount,并設(shè)置一個(gè)限制數(shù)量。

==一定要把全部的規(guī)則配置,否則對(duì)應(yīng)的share Extension中不會(huì)顯示APP==

3 提審AppStore的注意事項(xiàng)
  • 擴(kuò)展中的處理不能太長(zhǎng)時(shí)間阻塞主線程(建議放入線程中處處理),否則可能導(dǎo)致蘋果拒絕你的應(yīng)用。

  • 擴(kuò)展不能單獨(dú)提審,必須要跟容器程序一起提交AppStore進(jìn)行審核。

  • 提審的擴(kuò)展和容器程序的Build Version要保持一致,否則在上傳審核包的時(shí)候會(huì)提示警告,導(dǎo)致程序無(wú)法正常提審。

  • 如果你的APP要送審APPStore必須全部配置NSExtensionActivationRule,字段類型必須對(duì)應(yīng),否則會(huì)提交失敗,

  • 如果你的APP是用企業(yè)賬號(hào)分發(fā),==強(qiáng)烈建議不要使用group方式傳遞數(shù)據(jù)==,企業(yè)賬號(hào)分發(fā)會(huì)關(guān)閉group,導(dǎo)致不能數(shù)據(jù)傳輸,

4. 進(jìn)階研究

  • 4.1 對(duì)默認(rèn)分享界面進(jìn)行擴(kuò)展

在某些情況下,在分享界面中會(huì)加入一下其它信息的顯示,或者其它的選項(xiàng)供用戶操作。如:內(nèi)容要分享給什么好友、分享內(nèi)容的可見(jiàn)權(quán)限等等。那么,默認(rèn)的分享界面( SLComposeServiceViewController)提供了相關(guān)的方法來(lái)對(duì)其進(jìn)行擴(kuò)展。這些方法定義如下

      #if TARGET_OS_IPHONE
    /*
     Configuration Item Support (account pickers, privacy selection, location, etc.)
     */
        
    // Subclasses should implement this, and return an array of SLComposeSheetConfigurationItem instances, if if needs to display configuration items in the sheet. Defaults to nil.
    - (NSArray *)configurationItems;
    
    // Forces a reload of the configuration items table.
    // This is typically only necessary for subclasses that determine their configuration items in a deferred manner (for example, in -presentationAnimationDidFinish).
    // You do not need to call this after changing a configuration item property; the base class detects and reacts to that automatically.
    - (void)reloadConfigurationItems;
    
    // Presents a configuration view controller. Typically called from a configuration item's tapHandler. Only one configuration view controller is allowed at a time.
    // The pushed view controller should set preferredContentSize appropriately. SLComposeServiceViewController observes changes to that property and animates sheet size changes as necessary.
    - (void)pushConfigurationViewController:(UIViewController *)viewController;
    
    // Dismisses the current configuration view controller.
    - (void)popConfigurationViewController;
    #endif

其屬性說(shuō)明如下:

屬性 說(shuō)明
title 配置項(xiàng)標(biāo)題
value 當(dāng)前的配置值
valuePending YES時(shí),顯示值位置顯示加載動(dòng)畫,NO時(shí),顯示配置的值。
tapHandler 點(diǎn)擊配置項(xiàng)的事件處理

下面將通過(guò)使用這些方法來(lái)擴(kuò)展UI,使插件增加兩個(gè)配置項(xiàng):一個(gè)是是否公開(kāi)分享的配置項(xiàng),該選項(xiàng)標(biāo)識(shí)一個(gè)開(kāi)關(guān)值。另外一個(gè)是公開(kāi)權(quán)限設(shè)置項(xiàng),在是否公開(kāi)分享的開(kāi)關(guān)為開(kāi)時(shí)顯示??梢赃x擇分享給所有人還是好友。代碼如下所示:

- (NSArray *)configurationItems {
    // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.

    //定義兩個(gè)配置項(xiàng),分別記錄用戶選擇是否公開(kāi)以及公開(kāi)的權(quán)限,然后根據(jù)配置的值
    static BOOL isPublic = NO;
    static NSInteger act = 0;

    NSMutableArray *items = [NSMutableArray array];

    //創(chuàng)建是否公開(kāi)配置項(xiàng)
    SLComposeSheetConfigurationItem *item = [[SLComposeSheetConfigurationItem alloc] init];
    item.title = @"是否公開(kāi)";
    item.value = isPublic ? @"是" : @"否";

    __weak ShareViewController *theController = self;
    __weak SLComposeSheetConfigurationItem *theItem = item;
    item.tapHandler = ^{

        isPublic = !isPublic;
        theItem.value = isPublic ? @"是" : @"否";


        [theController reloadConfigurationItems];
    };

    [items addObject:item];

    if (isPublic)
    {
        //如果公開(kāi)標(biāo)識(shí)為YES,則創(chuàng)建公開(kāi)權(quán)限配置項(xiàng)
        SLComposeSheetConfigurationItem *actItem = [[SLComposeSheetConfigurationItem alloc] init];

        actItem.title = @"公開(kāi)權(quán)限";

        switch (act)
        {
            case 0:
                actItem.value = @"所有人";
                break;
            case 1:
                actItem.value = @"好友";
                break;
            default:
                break;
        }

        actItem.tapHandler = ^{

            //設(shè)置分享權(quán)限時(shí)彈出選擇界面
            ShareActViewController *actVC = [[ShareActViewController alloc] init];
            [theController pushConfigurationViewController:actVC];

            [actVC onSelected:^(NSIndexPath *indexPath) {

                //當(dāng)選擇完成時(shí)退出選擇界面并刷新配置項(xiàng)。
                act = indexPath.row;
                [theController popConfigurationViewController];
                [theController reloadConfigurationItems];

            }];

        };

        [items addObject:actItem];
    }

    return items;
    }

ShareActViewController 的實(shí)現(xiàn)

    @interface ShareActViewController () <UITableViewDelegate, UITableViewDataSource>

@property (nonatomic, strong) void (^selectedHandler) ();

@end

@implementation ShareActViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
    tableView.backgroundColor = [UIColor clearColor];
    tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    tableView.dataSource = self;
    tableView.delegate = self;
    [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"Cell"];
    [self.view addSubview:tableView];
}

- (void)onSelected:(void(^)(NSIndexPath *indexPath))handler
{
    self.selectedHandler = handler;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 2;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
    cell.backgroundColor = [UIColor clearColor];

    switch (indexPath.row)
    {
        case 0:
            cell.textLabel.text = @"所有人";
            break;
        case 1:
            cell.textLabel.text = @"好友";
            break;
        default:
            break;
    }

    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (self.selectedHandler)
    {
        self.selectedHandler (indexPath);
    }
}

在分享插件界面中重寫了configurationItems方法,然后定義了兩個(gè)配置項(xiàng)屬性,分別是是否公開(kāi)標(biāo)識(shí)isPublic和公開(kāi)權(quán)限act。然后創(chuàng)建是否公開(kāi)的SLComposeSheetConfigurationItem配置項(xiàng)和根據(jù)isPublic的值來(lái)判斷是否創(chuàng)建公開(kāi)權(quán)限配置項(xiàng)。其中是否公開(kāi)配置點(diǎn)擊時(shí)會(huì)變更isPublic的值,從而達(dá)到顯示或隱藏公開(kāi)權(quán)限配置。而公開(kāi)權(quán)限配置的點(diǎn)擊則彈出一個(gè)選擇的TableView,用于選擇給定的值然后返回到分享界面。

5. 替換Share Extension中的默認(rèn)分享界面

1、如果通過(guò)擴(kuò)展SLComposeServiceViewController還不能滿足需求的情況下,這時(shí)候就需要自己設(shè)計(jì)一個(gè)分享視圖控制器來(lái)替換默認(rèn)的SLComposeServiceViewController。

首先,創(chuàng)建一個(gè)自定義視圖控制器,如:CustomShareViewController。

2、然后打開(kāi)擴(kuò)展的Info.plist文件,刪除NSExtensionMainStoryboard屬性并增加一項(xiàng)NSExtensionPrincipalClass屬性并指向CustomShareViewController(注:這里沒(méi)有使用Storyboard所以要?jiǎng)h除該屬性),如圖:

image

3、接下來(lái)根據(jù)實(shí)際的需要來(lái)設(shè)計(jì)分享視圖的展示與交互形式。

4、然后調(diào)用CustomShareViewController的extensionContext屬性來(lái)控制擴(kuò)展的提交與取消等操作(注:由于擴(kuò)展中導(dǎo)入了關(guān)于ExtensionContext的UIViewController類目,因此,每個(gè)ViewController都帶有extensionContext屬性)。

為了演示的簡(jiǎn)單性,下面的代碼會(huì)通過(guò)extensionContext獲取到url后,給到自定義分享視圖的Label中顯示,同時(shí)也提供一個(gè)提交和取消按鈕,用于用戶對(duì)分享內(nèi)容的操作。代碼如下:

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    //定義一個(gè)容器視圖來(lái)存放分享內(nèi)容和兩個(gè)操作按鈕
    UIView *container = [[UIView alloc] initWithFrame:CGRectMake((self.view.frame.size.width - 300) / 2, (self.view.frame.size.height - 175) / 2, 300, 175)];
    container.layer.cornerRadius = 7;
    container.layer.borderColor = [UIColor lightGrayColor].CGColor;
    container.layer.borderWidth = 1;
    container.layer.masksToBounds = YES;
    container.backgroundColor = [UIColor whiteColor];
    container.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
    [self.view addSubview:container];

    //定義Post和Cancel按鈕
    UIButton *cancelBtn = [UIButton buttonWithType:UIButtonTypeSystem];
    [cancelBtn setTitle:@"Cancel" forState:UIControlStateNormal];
    cancelBtn.frame = CGRectMake(8, 8, 65, 40);
    [cancelBtn addTarget:self action:@selector(cancelBtnClickHandler:) forControlEvents:UIControlEventTouchUpInside];
    [container addSubview:cancelBtn];

    UIButton *postBtn = [UIButton buttonWithType:UIButtonTypeSystem];
    [postBtn setTitle:@"Post" forState:UIControlStateNormal];
    postBtn.frame = CGRectMake(container.frame.size.width - 8 - 65, 8, 65, 40);
    [postBtn addTarget:self action:@selector(postBtnClickHandler:) forControlEvents:UIControlEventTouchUpInside];
    [container addSubview:postBtn];

    //定義一個(gè)分享鏈接標(biāo)簽
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(8,
                                                               cancelBtn.frame.origin.y + cancelBtn.frame.size.height + 8,
                                                               container.frame.size.width - 16,
                                                               container.frame.size.height - 16 - cancelBtn.frame.origin.y - cancelBtn.frame.size.height)];
    label.numberOfLines = 0;
    label.textAlignment = NSTextAlignmentCenter;
    [container addSubview:label];

    //獲取分享鏈接
    __block BOOL hasGetUrl = NO;
    [self.extensionContext.inputItems enumerateObjectsUsingBlock:^(NSExtensionItem *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {

        [obj.attachments enumerateObjectsUsingBlock:^(NSItemProvider *  _Nonnull itemProvider, NSUInteger idx, BOOL * _Nonnull stop) {

            if ([itemProvider hasItemConformingToTypeIdentifier:@"public.url"])
            {
                [itemProvider loadItemForTypeIdentifier:@"public.url" options:nil completionHandler:^(id<NSSecureCoding>  _Nullable item, NSError * _Null_unspecified error) {

                    if ([(NSObject *)item isKindOfClass:[NSURL class]])
                    {
                        dispatch_async(dispatch_get_main_queue(), ^{

                            label.text = ((NSURL *)item).absoluteString;

                        });
                    }

                }];

                hasGetUrl = YES;
                *stop = YES;
            }

            *stop = hasGetUrl;

        }];

    }];
}

- (void)cancelBtnClickHandler:(id)sender
{
    //取消分享
    [self.extensionContext cancelRequestWithError:[NSError errorWithDomain:@"CustomShareError" code:NSUserCancelledError userInfo:nil]];
}

- (void)postBtnClickHandler:(id)sender
{
    //執(zhí)行分享內(nèi)容處理
    [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
}

share Extension 的基本內(nèi)容就是這樣了,

下面是Demo的地址;shareP

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容