Share Extension 开发指北

Share Extension(分享扩展)是一种iOS系统级扩展,该扩展使 iOS 应用间数据交换成为可能。


图片

除 Share Extension 外,iOS 系统级扩展还包括 Today Extension、Action Extension、Photo Editing Extension、Custom Keyboard 等,本文只讲 Share Extension 的基本用法。

言归正传

下面开始

一步步实现Share Extension。

Share Extension 不能单独创建,必须依赖于一个工程项目。

  1. 打开要增加扩展的项目,点击 File → New → Target

  2. 选择 Share Extension

  3. 填写 Product Name(产品名)及其他常用选项,点击Finish

  4. 在弹出窗口中选择“Activate”

这样,一个分享扩展就创建好了。

下面,我们可以开始编译运行这个扩展,与普通项目不一样的是:Share Extension 的运行需要选择一个Host APP(宿主应用,以下简称主程序)。这里以 Safari 为例,选中 Safari 并点击 Run,会启动 Safari ,打开任意一个网页,点击下方的

分享按钮

分享按钮,不出意外,这时候应该就可以看见你的分享扩展了。(出意外了?看下面的爬坑记录


从现在开始,我们讨论的前提就是已经创建好了 Share Extension


创建完 Share Extension 后,在项目目录中会生成一个 ShareExtension 文件夹,其中的 ShareViewController.swift 就是系统默认的入口文件。
ShareViewController 继承于 SLComposeServiceViewController,会附带一个默认的分享视图,点击分享按钮,然后点击刚创建的 Share Extension,弹出的分享弹框视图 即是。
此时如果点击 弹框 的 Post 按钮,程序会执行 didSelectPost 方法,我们只需要在此方法中实现分享逻辑即可。
这里难免会用到主程序的一些信息,比如 当前登录用户的 UserID 等。

下面,我们就来讨论一下

如何在 Share Extension 中获取主程序的信息

在默认情况下,iOS的应用是存在一个沙盒里面的,不允许应用之间直接进行数据的交互。
不过,对于开发者自己的应用,可以利用苹果提供的 App Groups 服务,在自己的应用之间进行数据传输。
一般来说,利用 App Groups 服务传输数据主要有 UserDefaultsFileManagerCoreData 三种方式。

使用 App Groups 服务

非常简单,只需在主程序项目配置中选择 Signing & Capabilities(Xcode 11以下是 Capabilities),添加 App Groups Capability ,然后增加一个App Groups 即可。

App Groups 跟Bundle ID 一样,只是通常以group.开头。

下面说一下

共享数据的三种方式

  1. UserDefaults
  • 创建

UserDefaults(suiteName: "your.app.groups.id"),注意不能用 UserDefaults.standard

  • 读取与写入

与正常的UserDefaults一致。
UserDefaults(suiteName: "your.app.groups.id")?.set("your.value", forKey: "YOURKEY")

  •    UserDefaults(suiteName: "your.app.groups.id")?.value(forKey: "YOURKEY")*
    
  1. FileManger
  • 创建

let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "your.app.groups.id")

  • 读取与写入

与正常的FileManager一致
// 写入
try FileManager.default.copyItem(at: srcURL, to: groupURL.appendingPathComponent(srcURL.lastPathComponent))
try "your.content".write(to: groupURL, atomically: true, encoding: .utf8)
// 读取
try String(contentsOf: groupURL)

  1. CoreData

其实CoreData是基于 FileManager 取得共享目录后来实现数据共享的,此处不多介绍。

通过这些方式,相信很容易可以拿到分享时需要的用户信息了,接下来,我们再来看一下

如何获取分享的内容

通过Share Extension唤醒的程序,可以通过 *self.extensionContext?.inputItems *获取到分享内容,示例代码如下:

struct MyError: Error {
    var localizedDescription: String
}

override func viewDidLoad() {
        super.viewDidLoad()

        for inputItems in self.extensionContext?.inputItems.compactMap({ $0 as? NSExtensionItem }) ?? [] {
            for itemProvider in inputItems.attachments ?? [] {
                if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
                itemProvider.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil) { (data, error) in
                        
                    guard error == nil else {
                            self.extensionContext?.cancelRequest(withError: error!)
                        return
                    }
                        
                    guard let element = data as? URL else {
                            self.extensionContext?.cancelRequest(withError: MyError(localizedDescription: "获取分享内容失败"))
                        return
                    }
                        
                    // element 就是分享内容的 URL,可以在此保存备用。
                        
                }
            }
        }
    }
}

有了内容跟用户信息,接下来只需要调用网络请求即可,以Alamofire库为例,通常,我们是用CocoaPods管理第三方库的,同样也可以

用CocoaPods管理 Share Extension 的第三方库

使用方法也非常简单,只需要在Podfile里增加

target 'ShareExtension' do
    pod 'Alamofire'
end

接下来,像开发主程序一样去做就可以了。

当然,主程序也可以跟Share Extension

共享代码

只需要选中需共享的文件,在右侧功能区的target里勾选 ShareExtension 即可。
如果你不嫌复制代码low的话,直接粘贴复制当然也可实现共享代码。

配置UI

ShareViewController 中的 configurationItems() 方法可以配置选择项,如分享到微信的 发送给朋友、分享到朋友圈、收藏 一样。
通常这个方法需要返回一个包含 SLComposeSheetConfigurationItem 的数组,
SLComposeSheetConfigurationItem 有 title,value, tapHandler, valuePending 四个属性,分别对应标题、值、点击事件、加载中提示四个功能。

如果需要跳转控制器,通常需要用到 *self.pushConfigurationViewController(_:) *方法。

自定义UI

如果自带的UI不能满足需求,可以完全自定义UI,只需将ShareViewController 继承于 UIViewController 即可。
值得注意的是,自定义UI时不要使用 UIScreen.main.bounds ,会拿不到数据。

至此,Share Extension 基本开发套路介绍完了。

强调几个需要注意的点

  1. 注意内存消耗

Share Extension 只有120M的内存空间可以使用(测试环境:Xcode 11,iOS 12.3.1),所以开发时请注意内存消耗,尤其不要向UserDefaults里大量写入数据,可以采用FileManager 的 *copyItem *保存要分享的内容。

  1. 注意上架要求

为减少上架时不必要的麻烦,建议:

  1. Share Extension 的 Info.plist 中,对NSExtensionAttributes做显式声明。

以网址为例,具体做法:
* 在Info.plist将NSExtensionActivationRule字段类型由String改为Dictionary。
* 展开NSExtensionActivationRule字段,创建其子项NSExtensionActivationSupportsWebURLWithMaxCount,并设置一个限制数量。

  1. 确保 Share Extension 的部署目标跟主程序部署目标一致。
  2. 注意Share Extension 的使用限制:
  3. 不能访问 sharedApplication 对象
  4. 不能使用任何标记NS_EXTENSION_UNAVAILABLE宏的API,或者类似的宏,或者不可用framework里面的API,例如HealthKit framework不能用于App extensions
  5. 不能访问相机或者麦克风(iMessage app可以访问这些资源,只要在Info.plist里面进行配置使用描述即可)
  6. 尽量不要运行一个长时间的后台任务(根据不同平台而异)
  7. 不能使用AirDrop接收数据

进阶

有些业务放在Share Extension 中可能会过于繁琐,会使 Share Extension 过于臃肿。于是乎,对于一些较为复杂的逻辑,或者对主程序依赖程度较高的功能,我们会考虑

在主程序中处理

例如:QQ的“发送给好友”就采用了该方案。

此方案思路较为明确:
首先,保存要分享的数据,;
然后,打开主程序;
最后,主程序获取到分享的数据并进行相关分享等操作。

保存与读取数据

我们可以用UserDefaults、FileManager、CoreData等任何一种你喜欢的方式保存或读取即可。忘了的可以回去看共享数据的三种方式

打开主程序

因为苹果爸爸是不允许 Share Extension 中使用 openURL的(Today Extension 跟 Message Extension可以),所以,这里我们需要用一点小技巧。

首先,配置URL Scheme。
在项目配置中,选择 Info,在URL Types 中输入 URL Schemes 即可。注意 URL Schemes 通常为纯英文字符,使用 _、*、&、%等特殊会导致无法打开程序(-、.可以使用)。

然后,就可以利用如下小技巧通过 URL Scheme 打开主程序
具体写法如下:

    /// 打开主APP
    /// - Parameter type: 打開類型
    func openContainerApp(type: String) {

        let scheme = "yoururlscheme://type"

        let url: URL = URL(string: scheme)!

        let context = NSExtensionContext()

        context.open(url, completionHandler: nil)

        var responder = self as UIResponder?

        let selectorOpenURL = sel_registerName("openURL:")

        while (responder != nil) {

            if responder!.responds(to: selectorOpenURL) {

                responder!.perform(selectorOpenURL, with: url)

                break

            }

            responder = responder?.next

        }

        self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)

    }

主程序打开后会调用AppDelegate 的 application(_:open:options:) 方法,可以用 url.host 获取到 ://后的type,以便进行相应的跳转或数据处理。

主程序打开了,分享的内容也有了,剩下的尽情发挥吧~

爬坑记录:

一、 Share Extension 不显示。

        第一次按步骤操作后,点击分享,Share Extension 始终无法在共享菜单里显示出来,尝试过以下几种方式:
  1. Share Extension 的 Info.plist 中,对NSExtensionAttributes做显式声明,发现并没用。
  2. Share Extension 的 Info.plist 中,增加App Transport Security Settings,设置Allow Arbitrary Loads 为 YES,也无效果;
  3. 使用正式版Xcode打开项目(之前用的是Beta版),无效;
  4. 更改 Share Extension 的部署目标跟主程序部署目标一致(iOS 10.0,iPhone),无效;

最终,通过在正式版 Xcode 中删除 Share Extension 并重新创建 Share Extension 问题得以解决,推测问题原因可能是 Beta 版 Xcode 对此存在 Bug,建议在开发时,尽量使用正式版 Xcode 进行开发,以免造成一些不必要的麻烦。

二、无法找到 kUTTypeText 等

kUTTypeText、kUTTypeImage、kUTTypeURL 在 CoreServices 内(iOS系统是 MobileCoreServices),需要

  1. import MobileCoreServices
  2. import CoreServices 后在 Linked Frameworks and Libraries中 Add CoreServices库

以上方法二选一,建议第一种。

三、无法通过 URL Schemes 启动程序

URL Schemes 中使用 _、*、&、%等特殊会导致无法打开程序,短线(-)、小数点(.)可以使用。
如果通过 URL Scheme 无法启动程序,可以在 Safari 地址栏中输入 yourURLScheme:// 试一下能否打开,如果Safari都打不开,就是URL Scheme配置问题,否则是唤起的代码存在问题。

附录

Info.plist 常用字段说明

Bundle display name 扩展的显示名称,默认跟你的项目名称相同,可以通过修改此字段来控制扩展的显示名称。
NSExtension 扩展描述字段,用于描述扩展的属性、设置等。作为一个扩展项目必须要包含此字段。
NSExtensionAttributes 扩展属性集合字段。用于描述扩展的属性。
NSExtensionActivationRule 激活扩展的规则。默认为字符串“TRUEPREDICATE”,表示在分享菜单中一直显示该扩展。可以将类型改为Dictionary类型,然后添加以下字段: NSExtensionActivationSupportsAttachmentsWithMaxCount NSExtensionActivationSupportsAttachmentsWithMinCount NSExtensionActivationSupportsImageWithMaxCount NSExtensionActivationSupportsMovieWithMaxCount NSExtensionActivationSupportsWebPageWithMaxCount NSExtensionActivationSupportsWebURLWithMaxCount
NSExtensionMainStoryboard 设置主界面的Storyboard,如果不想使用storyboard,也可以使用NSExtensionPrincipalClass指定自定义UIViewController子类名
NSExtensionPointIdentifier 扩展标识,在分享扩展中为:com.apple.share-services
NSExtensionPrincipalClass 自定义UI的类名
NSExtensionActivationSupportsAttachmentsWithMaxCount 附件最多限制,为数值类型。附件包括File、Image和Movie三大类,单一、混选总量不超过指定数量
NSExtensionActivationSupportsAttachmentsWithMinCount 附件最少限制,为数值类型。当设置NSExtensionActivationSupportsAttachmentsWithMaxCount时生效,默认至少选择1个附件,分享菜单中才显示扩展插件图标。
NSExtensionActivationSupportsFileWithMaxCount 文件最多限制,为数值类型。文件泛指除Image/Movie之外的附件,例如【邮件】附件、【语音备忘录】等。 单一、混选均不超过指定数量。
NSExtensionActivationSupportsImageWithMaxCount 图片最多限制,为数值类型。单一、混选均不超过指定数量。
NSExtensionActivationSupportsMovieWithMaxCount 视频最多限制,为数值类型。单一、混选均不超过指定数量。
NSExtensionActivationSupportsText 是否支持文本类型,布尔类型,默认不支持。如【备忘录】的分享
NSExtensionActivationSupportsWebURLWithMaxCount Web链接最多限制,为数值类型。默认不支持分享超链接,需要自己设置一个数值。
NSExtensionActivationSupportsWebPageWithMaxCount Web页面最多限制,为数值类型。默认不支持Web页面分享,需要自己设置一个数值。

参考文档:

  • 【iOS扩展开发攻略】Share Extension
  • iOS Share Extensions开发教程
  • 面向 Extension 开发 Share Extension
  • App extension 总结
  • App Extension Keys

你可能感兴趣的:(Share Extension 开发指北)