iOS相册实现Share Extension以及 Action Extension的功能

一. 实现背景

之前注意到相册中的图片分享的时候,可以选择不同的APP,但是由于时间问题,一直没有研究具体的实现方案。最近做的一个项目点击相册图片的分享的时候,需要直接打开APP,并且跳转到对应的界面,于是就研究了一下,原来是通过Share Extension跟Action Extension实现的。

二. App Extension

App Extension并不是一个独立的应用程序,他是包含在应用的Bundle里的一个独立包,它的后缀名为.appex。其中我们把提供App Extension的应用程序称为Containing App(容器应用),能够使用该扩展的应用称为Host App(宿主应用)。

需要注意的是,当手机中的容器应用被安装或卸载时,App Extension也会一起被安装或卸载。

在Host App中给App Extension提供了运行的上下文环境,并在响应用户操作时,发送请求启动扩展。App Extension通常在完成从Host App接收到请求后不久终止。具体的生命周期可以参考下图

三. Share Extension跟Action Extension是什么

简单说,就是如下图所示

在iOS中,Share Extension 用于允许用户从一个应用分享内容到另一个应用,而 Action Extension 则允许用户在一个应用中执行某些特定的操作,如处理文本、图像或链接。这两种扩展类型为用户提供了更加无缝的体验,使他们能够更方便地与应用之间共享和处理数据。

四. App Extension 通信

App Extension在启动和运行的过程中,App Extension以及Host App之间的通信如图所示。

具体来说就是,App Extension跟Containing App并没有进行直接的通信,Containing App 跟 Host App更不会有任何的联系。在 Host App中打开一个应用程序扩展,Container App会向App Extension发送一个请求,App Extension接收到数据后执行一些任务,完成任务后将处理结果返回给Host App。

五. Containing App与App Extension之间的交互及数据共享

App Extension与Containing App 之间存在着有限的交互方式。对于任何App Extension和Containing App,有一个私有的共享资源空间,他们可以访问其中的文件。App Extension、Containing App和Host App的完整通信如图所示:

六. Action Extension功能的具体实现

1. 创建Action Extension工程

在Containing App的工程中,点击菜单栏中的File->New->Target,出现如下的可选项,选择Action Extension:

2. 配置Extension的工程信息

3. 实现在相册直接打开App

a. 根据自己的需求,修改info.plist

我的项目只支持一张图片,于是我就修改为如下图

b. 由于我的功能是点击的时候就直接跳转App,代码的实现为

swift 复制代码
class ActionViewController: UIViewController {

    override func viewDidLoad() {

        super.viewDidLoad()

        for item in extensionContext?.inputItems as! [NSExtensionItem] {

            guard let providers = item.attachments else { return }

            for itemProvider in providers {

                if (itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String)) {

                    itemProvider.loadDataRepresentation(forTypeIdentifier: kUTTypeImage as String) { (data, error) in

                        if error != nil {

                            self.extensionContext?.cancelRequest(withError: error!)

                            return

                        }

                        guard let imgData = data else {

                            self.extensionContext?.cancelRequest(withError: ShareError(message: "data error"))

                            return

                        }

                        // 分享的图片

                        let ud = UserDefaults.init(suiteName: SUITNAME)

                        ud?.setValue(imgData, forKey: SHARE_IMAGE_KEY)

                        ud?.setValue(true, forKey: New_SHARE_KEY)

                        self.openContainerApp()

                    }

                }

            }

        }

        self.extensionContext!.completeRequest(returningItems: self.extensionContext!.inputItems, completionHandler: nil)

    }

    /// 打开ContainerAPP

    private func openContainerApp() {

        let scheme = "PandoraAI://"

        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

        }

    }

}

七、Share Extension功能的具体实现

1. 新建Share Extension项目

在Containing App的工程中,点击菜单栏中的File->New->Target,出现如下的可选项,选择Share Extension:

2. 配置Extension的工程信息

3. 实现在相册直接打开App

a. 根据自己的需求,修改info.plist

b. 由于我的功能是点击的时候就直接跳转App,代码的实现为

swift 复制代码
class ShareViewController: SLComposeServiceViewController {

    override func isContentValid() -> Bool {

        guard let inputItems = self.extensionContext?.inputItems.map({ $0 as? NSExtensionItem }) else {

            self.extensionContext?.cancelRequest(withError: ShareError(message: "extensionItem error"))

            return false

        }

        for inputItem in inputItems {

            guard let providers = inputItem?.attachments else { return false }

            for itemProvider in providers {

                if (itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String)) {

                    itemProvider.loadDataRepresentation(forTypeIdentifier: kUTTypeImage as String) { (data, error) in

                        if error != nil {

                            self.extensionContext?.cancelRequest(withError: error!)

                            return

                        }

                        guard let imgDataPath = data else {

                            self.extensionContext?.cancelRequest(withError: ShareError(message: "data error"))

                            return

                        }

                        // 分享的图片

                        let ud = UserDefaults.init(suiteName: SUITNAME)

                        ud?.setValue(imgDataPath, forKey: SHARE_IMAGE_KEY)

                        ud?.setValue(true, forKey: New_SHARE_KEY)

                        self.openContainerApp()

                    }

                }

            }

        }

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

        return true

    }

    override func didSelectPost() {

    }

    override func configurationItems() -> [Any]! {

        // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.

        return []

    }

    /// 打开ContainerAPP

    private func openContainerApp() {

        let scheme = "PandoraAI://"

        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

        }

    }

}
相关推荐
打小就很皮...6 分钟前
React 19 + Vite 6 + SWC 构建优化实践
前端·react.js·vite·swc
Highcharts.js8 分钟前
使用Highcharts与React集成 官网文档使用说明
前端·react.js·前端框架·react·highcharts·官方文档
这是个栗子9 分钟前
AI辅助编程(二) - 通译千问
前端·ai·通译千问
VT.馒头20 分钟前
【力扣】2625. 扁平化嵌套数组
前端·javascript·算法·leetcode·职场和发展·typescript
数研小生1 小时前
Full Analysis of Taobao Item Detail API taobao.item.get
java·服务器·前端
Shirley~~1 小时前
Vue-skills的中文文档
前端·人工智能
毎天要喝八杯水1 小时前
搭建vue前端后端环境
前端·javascript·vue.js
计算机程序设计小李同学2 小时前
幼儿园信息管理系统的设计与实现
前端·bootstrap·html·毕业设计
雨季6662 小时前
Flutter 三端应用实战:OpenHarmony “专注时光盒”——在碎片洪流中守护心流的数字容器
开发语言·前端·安全·flutter·交互
tao3556672 小时前
【用AI学前端】HTML-02-HTML 常用标签(基础)
前端·html