Share Extension
参考文档:
1.创建一个Share Extension
Extension不能单独创建必须依赖于应用工程项目,这个应用被称为Containing App(容器App)
- 创建Share Extension File->New->Target
然后会出现以下提示图,选择"Activate"至此,创建完成!
项目中多出以下内容:
.plist中有一个扩展标识:
2.运行Share Extension
选择taget然后选择提示框中的app来运行,因为是ShareExtension在其他App点击分享按钮并选中时就是运行ShareExtension的代码。这个其他App被称为Host App(宿主App)
若是没看到自己的扩展就点击旁边的More然后激活自己的扩展
- 扩展显示的名称可以在.plist文件的CFBundleDisplayName键的值进行修改。 An app extension needs a short, recognizable name that includes the name of the containing app, using the pattern < Containing app name >—< App extension name>. This makes it easier for users to manage extensions throughout the system. You can, optionally, use the containing app’s name as-is for your extension, in the common case that your containing app provides exactly one extension.
就会弹出
3.有时项目会有要求只能分享图片或者URL的需求可以通过配置info.plist(App Extension Keys官方详情文档)
设置分享数据类型
- 将info.plist的NSExtensionActivationRule字段修改成NSDictionary
- 根据需求设置分享类型已设置分享图片为例添加NSExtensionActivationSupportsImageWithMaxCount,并设置一个最大限制字数如下(如果查出限制数量的话,扩展会从选择行中徐傲视):(分享类型官方详情文档)
4.分享界面(本身)
Share Extension的分享界面即为下方的ShareViewController继承SLComposeServiceViewController控制器
分享界面:可以看出是由Cancel、Post按钮、文本编辑框和放置图片(附件)组成
- 文本编辑框属性
// SLComposeServiceViewController.h
// The textView displaying the (editable) text content on the sheet. When it's time to post, grab self.textView.text.
// SLComposeServiceViewController creates textView in -loadView and sets itself to be the textView's delegate.
#if TARGET_OS_IPHONE
@property (readonly, nonatomic) UITextView *textView;
#else
@property (readonly) NSTextView *textView;
#endif
// Convenience. This returns the current text from the textView.
@property (readonly, NS_NONATOMIC_IOSONLY) NSString *contentText;
// When textView is empty, this string is displayed instead.
// NOTE: This uses the iOS naming convention: "placeholder" vs. "placeholderString" on OS X.
@property (copy, NS_NONATOMIC_IOSONLY) NSString *placeholder;
- -isContentValid 判断当前内容是否符合条件可以post和监听文本编辑
- (BOOL)isContentValid {
// Do validation of contentText and/or NSExtensionContext attachments here
// 可以判断当前分享内容是否符合我们的要求,比如编辑的文本是否含有敏感字眼
if ([self.contentText containsString:@"我是敏感字眼"]) {
return NO; // 当前post按钮会处于不可用状态
}
return YES;
}
- Cancle、Post按钮点击事件
/*
Posting & Canceling
*/
// 点击Post触发事件
// The default implementation calls the extensionContext's -completeRequestReturningItems:completionHandler: method with nil.
// 子类必须实现这个方法,可以调用super方法不然的话实现上面的方法
- (void)didSelectPost;
// 点击Cancel触发事件可以重写自定义
// 默认实现调用extensionContext -cancelRequestWithError:NSError / NSUserCancelledError方法。
- (void)didSelectCancel;
// 点击Cancel触发事件然后在触发- didSelectCancel方法
// 子类不需要重写
- (void)cancel;
5.自定义分享界面
- 自定义一个视图控制器 MyShareExtensionViewControll
- 在info.plist中进行配置: 添加NSExtensionPrincipalClass对应自定义视图控制器类名; 删掉NSExtensionMainStoryboard Key对应着MainInterface;
- 在MyShareExtensionViewControll中根据具体需求进行设置
- (void)initShareView {
CGFloat bgViewWidth = SCREENWIDTH*0.6;
CGFloat bgViewHeight = bgViewWidth*1.25;
// 背景View
UIView *bgView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, bgViewWidth, bgViewHeight)];
bgView.center = CGPointMake(SCREENWIDTH/2, SCREENHEIGHT/2);
bgView.backgroundColor = [UIColor grayColor];
[self.view addSubview:bgView];
CGFloat buttonWidth = bgViewWidth/4;
CGFloat buttonHeight = 20;
// 取消按钮
UIButton *cancleButton = [[UIButton alloc] initWithFrame:CGRectMake(10, 10, buttonWidth, buttonHeight)];
[cancleButton setTitle:@"取消" forState:UIControlStateNormal];
[cancleButton addTarget:self action:@selector(cancelButtonAction:) forControlEvents:UIControlEventTouchUpInside];
[bgView addSubview:cancleButton];
// post按钮
UIButton *postButton = [[UIButton alloc] initWithFrame:CGRectMake(bgViewWidth-10-buttonWidth, 10, buttonWidth, buttonHeight)];
[postButton setTitle:@"发送" forState:UIControlStateNormal];
[postButton addTarget:self action:@selector(postButtonAction:) forControlEvents:UIControlEventTouchUpInside];
[bgView addSubview:postButton];
self.bgView = bgView;
[self initAttachImageView];
}
- (void)initAttachImageView {
// 展示分享的图片
NSExtensionItem *item = self.extensionContext.inputItems.firstObject;
NSDictionary *dic = item.userInfo;
NSArray *providerArray = dic[NSExtensionItemAttachmentsKey];
NSItemProvider *provider = providerArray[0];
if ([provider hasItemConformingToTypeIdentifier:@"public.image"]) {
[provider loadItemForTypeIdentifier:@"public.image" options:nil completionHandler:^(id<NSSecureCoding> _Nullable item, NSError * _Null_unspecified error) {
NSData *imageData = [NSData dataWithContentsOfURL:(NSURL *)item];
if (imageData) {
UIImage *image = [UIImage imageWithData:imageData];
// 图片imageView
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.frame = CGRectMake(0, 50, self.bgView.frame.size.width, (self.bgView.frame.size.width*image.size.height)/image.size.width);
[self.bgView addSubview:imageView];
}
}];
}
}
- (void)cancelButtonAction:(UIButton *)button {
[self.extensionContext cancelRequestWithError:[NSError errorWithDomain:@"CustomShareError" code:NSUserCancelledError userInfo:nil]];
}
- (void)postButtonAction:(UIButton *)button {
// 处理
[self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
}
如图:
6.获取数据
- 附件
// SLComposeServiceViewController.h
/*
Sheet Content
NOTE: Attachments (e.g., links, images, videos) and initial text are accessible via the view controller's extensionContext.
*/
// 可知要获取分享的连接、图片、视频等可以从extensionContext中实现
- NSExtensionContext(扩展上下文)
// 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);
// UIViewController控制器在iOS8.0之后都有这个属性,非扩展请求获取到的extensionContext为nil,可用来判断是否参与扩展请求
- NSExtensionContext
// 1.获取扩展内容
// 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; // 可知数组中存放的是NSExtensionItem
// 2.有一个key
// Key in userInfo. Value is a dictionary of NSExtensionItems and associated NSError instances.是NSExtensionItem的userInfo(NSDictionary *)的中错误信息对应的Key
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionItemsAndErrorsKey API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
// 3.有的需求是在扩展中打开Containing App)(或者是打开一个URL)
> 官方文档中说明Today 扩展才可以,并且在提交审核的时候要说明
// Asks the host to open an URL on the extension's behalf
- (void)openURL:(NSURL *)URL completionHandler:(void (^ _Nullable)(BOOL success))completionHandler;
// 4.定义了关于Host App的四个通知
// Host App将要进入
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionHostWillEnterForegroundNotification API_AVAILABLE(ios(8.2), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macos);
// Host App已经进入后台
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionHostDidEnterBackgroundNotification API_AVAILABLE(ios(8.2), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macos);
// The host process will resign active status (stop receiving events), the extension may be suspended.Host App将要被挂起
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionHostWillResignActiveNotification API_AVAILABLE(ios(8.2), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macos);
// The host process did become active (begin receiving events).Host App已经被激活
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionHostDidBecomeActiveNotification API_AVAILABLE(ios(8.2), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macos);
- NSExtensionItem
// (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; // 扩展中分享的内容(附件)就存放在attachments,这些附件类型为NSItemProvider
// (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; // NSExtensionItem的上面三个属性值和key都会在userInfo一一映射
// userInfo的key
FOUNDATION_EXTERN NSString * _Null_unspecified const NSExtensionItemAttributedTitleKey API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
FOUNDATION_EXTERN NSString * _Null_unspecified const NSExtensionItemAttributedContentTextKey API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
FOUNDATION_EXTERN NSString * _Null_unspecified const NSExtensionItemAttachmentsKey API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
- NSItemProvider获取到了附件,对不同的附件可能有不同处理,所以我们可以判断附件类型
// 1.判断附件是否存在某种类型的item
// Returns YES if the item provider has at least one item that conforms to the supplied type identifier.
- (BOOL)hasItemConformingToTypeIdentifier:(NSString *)typeIdentifier; // 判断是否含有指定typeIdentifier类型的item存在,返回BOOL
// 2.加载item
- (void)loadItemForTypeIdentifier:(NSString *)typeIdentifier options:(nullable NSDictionary *)options completionHandler:(nullable NSItemProviderCompletionHandler)completionHandler;
- 获取图片例子(其他类型一样)
__weak typeof(self) weakSelf = self;
NSExtensionItem *extension = self.extensionContext.inputItems.firstObject;
NSArray *providerArray = extension.userInfo[NSExtensionItemAttachmentsKey];
[providerArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSItemProvider *provider = providerArray[0];
if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) { // 判断是否含有图片类型的provider
[provider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:nil completionHandler:^(id<NSSecureCoding> _Nullable item, NSError * _Null_unspecified error) {
NSData *imageData = [[NSData alloc] initWithContentsOfURL:(NSURL *)item]; // 选中的data
}];
}
}];
7.Extension和Containing App数据共享
iOS中应用不允许应用与应用直接进行数据的交互,提供给了App Groups来进行数据交互,App Groups可以通过NSUserDefaultsUse(Core Data, SQLite, or Posix locks to help coordinate data access in a shared container.)来进行相互的数据交互.
先激活App Groups服务
Containging App激活操作如下- 开发者后台创建App ID,打开App Groups
创建App Groups
勾选上创建的group
扩展激活也是一样的只不过不需要创建新的app group,勾选和Containing App同一个group就行
- 开发者后台创建App ID,打开App Groups
数据交互操作
- NSUserDefaults
// 1.存入 // 初始化一个给App Groups使用的NSUserDefaults,一定要用initWithSuiteName:方法,取出也是. SuiteName就是刚才勾选的group NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.xxx"]; [defaults setObject:<#(nullable id)#> forKey:<#(nonnull NSString *)#>]; [defaults synchronize]; // 2.取出 NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.xxx"]; id data = [defaults objectForKey:<#(nonnull NSString *)#>];
- NSFileManager
// 1.存入 // 打开指定group的目录 NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.xxx"]; NSURL *imageURL = [groupURL URLByAppendingPathComponent:@"image"]; [data writeToURL:imageURL atomically:YES]; // 2.取出 // 打开指定group的目录 NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.xxx"]; NSURL *imageURL = [groupURL URLByAppendingPathComponent:@"image"]; NSData *data = [NSData dataWithContentsOfURL:<#(nonnull NSURL *)#>];
// 继续研究在扩展中数据的上传和下载怎样处理