「RSHARE」 一键分享 iOS 版

因为公司的项目里集成了一键分享的这个模块, 而在我设计的时候发现国内的官方文档和提供的 Sample 有混乱和容易混淆的地方, 而且除了普通的网页、图片、文字分享到各大 Social 平台以外, 对于视频、文件和其他内容的分享 Demo 在百度或者 Google 几乎搜不到. 自己也是踩了很多坑才把很多问题解决.

测试设备

iPhone 7 Plus, iOS 11.4.1.

支持平台

RSHARE 这个 Demo 中支持: 微信、QQ、新浪微博、Facebook、GooglePlus(Google +)、Twitter、WhatsApp、Line、Tumblr、Instagram、Pinterest 11 个 Social 平台.

平台差异

主要罗列了常用的 5 个分享内容的对比菜单(网页、文字、图片、本地视频、文件).
注意菜单内字母以及菜单后面对应字母的注释⚠️

❤️ QQ 微信 微博 Facebook Twitter Instagram WhatsApp Line Tumblr Pinterest Google+
网页 ✓a ✓b
文字 ✓c
图片 ✓d ✓d
视频 ?e
文件

a. QQ 虽支持网页分享, 但是不允许带着网页的 description 字段, 且分享到 QQ 空间只支持通过文字分享, 这和 Android 表现不同;
b. Facebook 的网页分享支持 hashtag;
c. iOS 端分享文字到 QQ 客户端是可行的, 但是 Android 端不允许, 其实 QQ 的分享目地就是让用户自行输入有价值、有意义的文字信息, 这样可以过滤一部分广告, 但是 iOS 和 Android 端的不统一实在是不应该;
d. Pinterest 和 Tumblr 虽支持图片分享, 但仅仅支持分享图片的 URL, 它会自行解析并显示;
e. QQ 客户端分享分两类: 1 好友、收藏、数据线(我的电脑)分享; 2 QQ 空间分享. 本地视频的分享只支持 QQ 空间分享.

  1. 对比列表得知, 国内的平台分享内容是最丰富的, 但是存在一个主要的问题(主要是 QQ), iOS 和 Android 双端的接口以及实现的功能也并不统一(后面的部分会具体说); 微信双端当分享的图片过大的时候的表现也有不统一的时候, 其余都很完善; 新浪的表现是最统一的, 且没有过多限制; 国内的平台分享最让人头疼的就是官方的 Sample 很混乱, 但是在实践代码的时候还是要以官方为主.
  2. 国外的文档和 Sample 很明了且注释详细, 但也存在双端不统一的情况, Twitter 的双端表现就不一样(后面细说), 但是国外的平台分享几乎没有回调, Facebook 的回调只有 Feed 形式的分享才有效, Instagram、Line、Google+这些是没有 SDK 的, 仅仅是通过打开Application URL scheme 来分享.

分享总体设计

大概的分享逻辑如下:


iOS 分享逻辑.png

iOS 的分享是参照各大平台的分享逻辑得到比较统一思路, 即: 先判断是否安装对应应用, 然后初始化 SDK 与官方平台连接, 然后包装分享参数进行分享, 最后处理分享回调.

  1. 有些平台不存在 SDK, 所以直接判断是否平台安装然后包装分享参数进行分享;
  2. Tumblr 比较特殊, 有 SDK 但是不需要判断应用是否安装就可以分享, Twitter 也如此, 具体情况在 Twitter 的部分说明;
  3. SDK 的初始化, 统一函数名字为 sdkInitialize, KeySecret以及AppID等信息在注释中有标明.

详细逻辑

关于各个平台的开发者主页和文档信息以这里为主, 代码注释的可能不准确.

平台分享都是通过单例模式实现.

在手动导入框架的时候尽量, 放在项目根目录下然后通过下图的方式导入:

Snip20180828_10.png

在项目级 Framework 目录下右键执行 Add Files to "name".

子平台分享完毕返回到本应用的接口再封装 Objective-C 版本用的是实例方法, Swift 版本使用的是类方法.

基类

基类都是继承自RShare, 这个类中定义了分享 Mode(代码注释中有标明, iOS 中使用范围较小)、分享结果 ShareResult (成功、取消、失败)以及最重要的回调.

回调:
Objective-C:

typedef void (^RShareCompletion)(RShareSDKPlatform platform, ShareResult result, NSString* _Nullable errorInfo);

Swift:

typealias RShareCompletion = (_ paltform : RShareSDKPlatform,_ result : ShareResult,_ errorInfo : String?) -> Void

Mode(仅对 Facebook、Twitter、Instagram 有效, Android 亦然):
Objective-C:

typedef NS_ENUM(NSInteger, Mode) {
    /**
     @Displays 优先选择原生应用分享, 原生应用未安装的情况可能跳转内置 WebView 或者 Safari 进行分享.
     */
    ShareModeAutomatic,
    /**
     @Displays 原生应用分享.
     */
    ShareModeNative,
    /**
     @Displays 应用内置 UIWebView 分享.
     */
    ShareModeWeb,
    /**
     @Displays the dialog in the iOS integrated share sheet, 仅对 Facebook 分享有效.
     */
    ShareModeSheet,
    /**
     @Displays 跳转至 Safari 分享, 仅对 Facebook 分享有效.
     */
    ShareModeBrowser,
    /**
     @Displays 跳转至 Safari 进行 Feed 形式的分享, 仅对 Facebook 分享有效.
     */
    ShareModeFeedBrowser,
    /**
     @Displays 应用内置 UIWebView 的 Feed 形式分享, 仅对 Facebook 分享有效.
     */
    ShareModeFeed,
    /**
     @Displays iOS 的系统分享.
     */
    ShareModeSystem
};

QQ

准备

分享需要注册平台, 腾讯开发者主页, SDK 下载, QQ SDK 目前不支持 pod 安装, iOS API 调用说明文档.

集成

a. TencentOpenAPI.framework导入项目中;
b. 添加系统依赖Security.frameworkSystemConfiguration.frameworkCoreGraphic.frameworklibsqilte3.0.tbdCoreTelephony.frameworklibz.tbd.
c. 设置 The Other Flags-ObjC.
d. 在info.plist文件的CFBundleURLTypes中添加:

<key>CFBundleURLSchemes</key>
<array>
    <string>tencentYOURAPPID</string>
</array>

e. 添加以下至白名单:

<string>mqq</string>
<string>mqqapi</string>
<string>mqqwpa</string>
<string>mqqbrowser</string>
<string>mttbrowser</string>
<string>mqqOpensdkSSoLogin</string>
<string>mqqopensdkapiV2</string>
<string>mqqopensdkapiV3</string>
<string>mqqopensdkapiV4</string>
<string>wtloginmqq2</string>
<string>mqzone</string>
<string>mqzoneopensdk</string>
<string>mqzoneopensdkapi</string>
<string>mqzoneopensdkapi19</string>
<string>mqzoneopensdkapiV2</string>
<string>mqqapiwallet</string>
<string>mqqopensdkfriend</string>
<string>mqqopensdkdataline</string>
<string>mqqgamebindinggroup</string>
<string>mqqopensdkgrouptribeshare</string>
<string>tencentapi.qq.reqContent</string>
<string>tencentapi.qzone.reqContent</string>

f. Swift 语言集成需要 Objective-C - Swift 桥接文件.

接口调用及内部实现

a. 初始化 SDK

Objective-C:

[[RQqManager shared] sdkInitializeByAppID:yourAppId appKey:yourAppKey];

Swift:

RQqManager.shared.sdkInitialize(appID: yourAppId, appKey: yourAppKey)

内部实现 (Objective-C):

[[TencentOAuth alloc]initWithAppId:appID andDelegate:self];

由此看出, 其实仅仅是用作分享功能的话, 是不需要 appKey 这个字段的.

b. 分享

对于分享内容的包装, 通过建立 RQqHelper 来实现分享内容的处理和平台 Manager 的分隔, 这样有利于逻辑的处理, 结构明显, 代码可控.
关于参数包装的细节, 没什么好细说的, QQ 提供的接口简单明了(Android 不是这样).

另, 除在 QQApiInterfaceDelegate 代理函数 - (void)onResp:(QQBaseResp *)resp 中处理分享回调外, 在发起手 Q 分享请求的接口 [QQApiInterface sendReq: request] 返回的 QQApiSendResultCode 做了分享失败的请求码处理, Objective-C 中单独建立的处理函数 - (void)handleResultCode:(QQApiSendResultCode)code, Swift 则直接在 resultCodeset 方法处理.

分享到 QQ 的接口中 scene 字段细分到 QQ 好友、我的收藏、数据线(我的电脑), 但 Android 中不支持直接打开‘我的收藏’和‘数据线(我的电脑)’面板, 须自行选择.

文字分享:

Objective-C:

[[RQqManager shared] shareTextToQQ:text scene:scene completion:completion];

Swift:

RQqManager.shared.share(text: text, scene: scene, completion: completion)

表现(QQ 好友分享):


image.png

图片分享:

Objective-C:

[[RQqManager shared] shareImageToQQ:targetImage title:title description:description scene:scene completion:completion];

Swift:

RQqManager.shared.share(image: targetImage, title: title, description: description, scene: scene, completion: completion)

表现(QQ 好友分享):


image.png

网页分享:

Objective-C:

[[RQqManager shared] shareWebpageToQQWithURL:webpageURL title:title description:description thumbImage:image scene:scene completion:completion];

Swift:

RQqManager.shared.share(webpageURL: webpageURL, title: title, description: description, thumbImage: image, scene: scene, completion: completion)

表现(QQ 好友分享):


image.png

视频链分享:
实质就是网页的分享, 在此不作代码示例.

音频链分享:
大致和网页的分享相同, 但是多了一个 streamURL 的字段.

Objective-C:

[[RQqManager shared] shareAudioToQQWithStreamURL:audioStreamURL title:title description:description thumbImage:image webpageURL:webpageURL scene:scene completion: completion];

Swift:

RQqManager.shared.share(audioStreamURL: audioStreamURL, title: title, description: description, thumbImage: image, webpageURL: webpageURL, scene: scene, completion: completion)

⚠️ 1.Tencent.frameworkQQApiVideoObjectflashURL 属性设置音频流.
⚠️ 2. audioStreamURL 请设置音频的音频流链接, 不要把音乐平台分享的音乐网链直接赋值在该字段上, 否则点击播放按钮是无法播放的, 音乐网链是放在 webpageURL 字段上的.

表现(QQ 好友分享):


image.png

文件分享:

Objective-C:

[[RQqManager shared] shareFileToQQWithFileData:fileData fileName:fileName title:title description:description thumbImage:image completion: completion];

Swift:

RQqManager.shared.share(fileData: filedata, fileName: fileName, title: title, description: description, thumbImage: image, compeltion: completion)

⚠️ 1. fileName 字段设置方式是: ‘文件名’+‘.扩展名’, 形如:HelloWorld.doc, 这样 QQ 系统内部能够解析并且可以友好的展示分享内容, 经测试大部分常规的文件都能友好的展示.
⚠️ 2. 文件分享只支持 QQ 客户端数据线(我的电脑)分享, 并且 Android 端无法分享文件, 也或许其实是可以分享文件只不过本人技拙没发现实现方法罢.

表现(以视频为例):


image.png
image.png

文字分享到 QQ 空间:

Objective-C:

[[RQqManager shared] shareTextToQZone:description completion: completion];

Swift:

RQqManager.shared.share(text: description, completion: completion)

⚠️ Android 端不支持纯文字分享到 QQ 空间, 且 iOS 端不支持网页分享到 QQ 空间, 除非把字符串形式的网链通过文字的方式分享, 这是可行的.

表现:


image.png

分享图片到 QQ 空间:

Objective-C:

[[RQqManager shared] shareImagesToQZone: targetImageArray  description:description completion: completion];

Swift:

RQqManager.shared.share(images: targetImageArray, description: description, completion: completion)

⚠️ description 字段实际是失效的, 但不保证未来一定不用这个字段, 所以暂且保留这个字段.

表现:

image.png

分享本地视频到 QQ 空间:

Objective-C:

[[RQqManager shared] shareVideoToQZoneWithAssetURL:videoAssetURL description:description completion: completion];

Swift:

RQqManager.shared.share(videoAssetURL: videoAssetURL, description: description, completion: completion)

⚠️ 1. description 字段实际是失效的.
⚠️ 2. 本地视频 URL 为通过 UIImagePickerController 选择的媒体 infoUIImagePickerControllerReferenceURL 的值, 形如: assets-library://asset/asset.MP4?id=8FF2F03F-DD84-41A5-A20C-B745E793C0DC&ext=MP4

表现:


image.png

c. 返回本应用

Objective-C:

[[RQqManager shared] application:app openURL:url options: options];

Swift:

RQqManager.application(app, open: url, options : options)

微信

准备

分享需要注册平台, 微信开放平台, SDK 下载, 微信 SDK 支持 pod 安装, 分享 & 收藏 API 调用说明.

集成

a. 手动: libWeChatSDK.aWXApi.hWXApiObject.h, 导入项目中;
pod 集成: pod 'WechatOpenSDK', 若出现:

Use the (inherited) flag, or Remove the build settings from the target. 🔧解决方法(引自[微信集成说明](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=1417694084&token=&lang=zh_CN), 未亲自测试): 把工程 target 中的 build Setting 里面 **PODS_ROOT** 的值替换成 **\(inherited)**, Other Linker Flags-all_load 替换成 $(inherited).

b. 添加系统依赖 SystemConfiguration.framework, libz.dylib, libsqlite3.0.dylib, libc++.dylib, Security.framework, CoreTelephony.framework, CFNetwork.framework.
c. 手动集成的情况下, 需设置 The Other Flags-ObjC.
d. 在info.plist 文件的 CFBundleURLTypes 中添加:

<key>CFBundleURLSchemes</key>
<array>
    <string>wxYOURAPPID</string>
</array>

e. 添加以下至白名单:

<string>weixin</string>
<string>wechat</string>

f. Swift 语言集成需要 Objective-C - Swift 桥接文件.

接口调用及内部实现

a. 初始化 SDK

Objective-C:

[[RWechatManager shared] sdkInitializeByAppID:appID appSecret:secret];

Swift:

RWechatManager.shared.sdkInitialize(appID: appID, appSecret: secret)

仅做分享功能的话, secret 字段无用.

初始化内部实现略微有区别:

Objective-C:

UInt64 typeFlag = MMAPP_SUPPORT_TEXT | MMAPP_SUPPORT_PICTURE | MMAPP_SUPPORT_VIDEO | MMAPP_SUPPORT_LOCATION | MMAPP_SUPPORT_AUDIO | MMAPP_SUPPORT_WEBPAGE; 
[WXApi registerAppSupportContentFlag:typeFlag];
[WXApi registerApp:appID];

Objective-C 中同时设置多个枚举值可以通过 '|' 来实现 option 的设置.

Swift:

let typeFlag : TypeFlag = [.Text, .Picture, .Video, .Audio, .Webpage]
WXApi.registerAppSupportContentFlag(typeFlag.rawValue)
WXApi.registerApp(appID)

Swift 中设置多个枚举值不可用 '|' 来实现, 想要达到这样的效果, 必须新建结构体并且实现 OptionSetType 协议, OptionSetType 改变了 Objective-C 中 NS_ENUM / NS_OPTIONS 的行为方式赋予可多选的能力.

TypeFlag 的声明(没有写全, 但实际测试并不影响文件的分享):

private struct TypeFlag : OptionSet {
    
    let rawValue: UInt64
    
    static let Text = TypeFlag(rawValue: enAppSupportContentFlag.MMAPP_SUPPORT_TEXT.rawValue)
    static let Picture = TypeFlag(rawValue: enAppSupportContentFlag.MMAPP_SUPPORT_PICTURE.rawValue)
    static let Video = TypeFlag(rawValue: enAppSupportContentFlag.MMAPP_SUPPORT_VIDEO.rawValue)
    static let Audio = TypeFlag(rawValue: enAppSupportContentFlag.MMAPP_SUPPORT_AUDIO.rawValue)
    static let Webpage = TypeFlag(rawValue: enAppSupportContentFlag.MMAPP_SUPPORT_WEBPAGE.rawValue)     
}

b. 分享

微信分享的内容包装同样通过一个 RWechatHelper 进行单独处理, 对于文字、网页、图片、视频网链、小程序、文件的分享内容处理都很简单, 特别注意的就是对于音乐链的分享, 同 QQ 一样, 需要区别两个参数, 一个是音频流链, 一个是音频网页, 播放的是音频流, 点击背景进入的是音频网页.

Objective-C:

WXMusicObject* obj = [WXMusicObject object];
obj.musicDataUrl = musicURL;
obj.musicUrl = webpageURL;

Swift:

let obj = WXMusicObject()
obj.musicUrl = audioStreamURL
obj.musicUrl = webpageURL

文字分享:

Objective-C:

[[RWechatManager shared] shareText:text scene: scene completion: completion];

Swift:

RWechatManager.shared.share(text: shareDescription, scene: scene, completion: shareCompletion)

表现(分享到好友):

image.png

图片分享:

Objective-C

[[RWechatManager shared] shareImage:targetImage scene: scene completion: completion];

Swift:

RWechatManager.shared.share(image: targetImage, scene: scene, completion: completion)

表现(分享到好友列表):

image.png

网页分享:

Objective-C:

[[RWechatManager shared] shareWebpageWithURL:webpageURL title:title description:description thumbImage:thumbImage scene: scene completion: completion];

Swift:

RWechatManager.shared.share(webpageURL: webpageURL, title: title, description: description, thumbImage: thumbImage, scene: scene, completion: completion)

⚠️ 1. thumbImage 字段的缩略图大小不能超过 32 Kb, 但实际测试 100 Kb 左右也是完全可行的, 但一定不能过大, 否则会出现:分享 N 次才能成功一次, 或者干脆无法调起微信的客户端进行分享.
⚠️ 2. Android 对于缩略图的处理相对友好很多, 对于 iOS 无法分享的过大的缩略图数据一般情况下 Android 都能成功启动微信客户端并且成功分享(亲测).
⚠️ 3. 在缩略图没有符合规范的时候, 即使成功分享在 iOS 端也会出现缩略图不显示的情况, Android 几乎都会显示.

表现(分享到好友):


image.png

视频链分享:
实质就是网页的分享, 在此不作代码示例.

音频链分享:

Objective-C:

[[RWechatManager shared] shareMusicWithStreamURL: audioStreamURL webpageURL:audioWebpageURL title: title description: description thumbImage:image scene:scene completion: completion];

Swift:

RWechatManager.shared.share(audioStreamURL: audioStreamURL, webpageURL: audioWebpageURL, title: title, description: description, thumbImage: thumbImage, scene: scene, completion: completion)

⚠️ 注意 audioStreamURLwebpageURL 的区别, 前面有提及.

表现(分享到微信好友):


image.png

小程序分享:

Objective-C:

[[RWechatManager shared] shareMiniProgramWithUserName:userName path:path type:type webpageURL:webpageURL title:title description:description thumbImage:image scene: scene completion: completion];

Swift:

RWechatManager.shared.shareMiniProgram(userName: userName, path: path, type: type, webpageURL: webpageURL, title: title, description: description, thumbImage: thumbImage, scene: scene, completion: completion)

⚠️ 1. demo 中笔者并没有编写小程序, 仅仅依照微信的 SDK 进行了参数的设置, 所以无法分享, 但具体的参数设置形式在 demo 中明确标明.
⚠️ 2. 小程序分三种类型, type 字段分: 发布预览以及体验三个版本.

文件分享:

Objective-C:

[[RWechatManager shared] shareFileWithData:fileData extension:fileExtensionName title:title thumbImage:image scene:scene completion: completion];

Swift:

RWechatManager.shared.share(fileData: fileData, extensionName: fileExtensionName, title: title, thumbImage: thumbImage, scene: scene, completion: completion)

对于体积稍大的图片、小体积视频、doc 文本、PDF 文件 都测试过, 可以正常分享, 且通过微信内部的解析可以显示.

⚠️ 1. 必须设置文件的扩展名.
⚠️ 2. 文件分享不支持分享到朋友圈.
⚠️ 3. 在设置缩略图的情况下, iOS 端的对话框是不显示缩略图的, 但是 Android 可以, 但也仅限于发送方能看见.

表现(分享视频到微信好友):


image.png
image.png

c. 返回本应用

Objective-C:

[[RWechatManager shared] application:app openURL:url options:options];

Swift:

RWechatManager.application(app, open: url, options : options)

新浪

准备

分享需要注册平台, 新浪开放平台, SDK 下载, 新浪 SDK 支持 pod 安装, iOS 接口调用文档.

集成

a. 手动导入 WeiboSDK.hWBHttpRequest.hlibWeiboSDK.aWeiboSDK.bundle 到项目中.
pod 集成: pod "Weibo_SDK", :git => "https://github.com/sinaweibosdk/weibo_ios_sdk.git" (未实际测试过).

b. 添加系统依赖QuartzCore.frameworkSystemConfiguration.frameworkImageIO.frameworkCoreGraphic.frameworkSecurity.frameworklibsqilte3.0.tbdCoreTelephony.frameworkCoreText.frameworklibz.tbd.
c. 设置 The Other Flags-ObjC.
d. 在 info.plist 文件的 CFBundleURLTypes 中添加:

<key>CFBundleURLSchemes</key>
<array>
    <string>wbYOURAPPKEY</string>
</array>

e.对传输安全的支持, 在当下的 iOS 系统中,默认需要为每次网络传输建立 SSL, 所以需在 plist 中设置 NSAppTransportSecurity 的 NSAllowsArbitraryLoadsYES.

f. 解除原有 ATS设置在 iOS 10+ 的网络限制:

<key>sina.com.cn</key>
    <dict>
        <key>NSIncludesSubdomains</key>
        <true/>
        <key>NSThirdPartyExceptionAllowsInsecureHTTPLoads</key>
        <true/>
        <key>NSExceptionMinimumTLSVersion</key>
        <string>TLSv1.0</string>
        <key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
        <false/>
    </dict>

g. 添加以下至白名单:

<string>sinaweibohd</string>
<string>sinaweibo</string>
<string>weibosdk</string>
<string>weibosdk2.5</string>

h. Swift 语言集成需要 Objective-C - Swift 桥接文件.

接口调用及内部实现

a. 初始化 SDK

Objective-C:

[[RSinaWeiboManager shared] sdkInitializeByAppKey:YourAppKey appSecret:YourAppSecret];

Swift:

RSinaWeiboManager.shared.sdkInitialize(appKey: YourAppKey, appSecret: YourAppSecret)

仅做分享的话, secret 字段无用.

b. 分享

对于分享内容的包装新浪处理的比 QQ 简练, 所以无需借助新的类去处理分享内容, 文字是最简单的, 不需要对文字内容进行二次包装, 直接对 WBMessageObject 进行文字设置然后发送分享请求即可;

图片和视频(包括到「微博故事」)的分享需要 WBImageObjectWBNewVideoObject 的包装, 执行 addImagesaddVideo 方法将要分享的图片/视频处理, 在此需要注意的是:实例化 WBImageObjectWBNewVideoObject 的时候必须设置 delegate, 并实现 WBMediaTransferProtocol 协议, 由于 addImagesaddVideo 是异步操作, 所以 wbsdk_TransferDidReceiveObject 回调中发送分享请求!

另, 经测试, 在参数准备失败的回调函数 wbsdk_TransferDidFailWithErrorCode 中, 出现的错误场景总结:

  • 图片、视频体积过大;
  • 图片数量不在 1 ~ 9 范围内;
  • 倘若分享到「微博故事」, 照片只能分享一张, 而传了多张图片;
  • WBImageObject 多参数并存: 单张的 Data 格式的图片和图片数组共存;
  • 视频 URL 错误.

文字分享:

Objective-C:

[[RSinaWeiboManager shared] shareText: text completion: completion];

Swift:

RSinaWeiboManager.shared.share(text: text, completion: completion)

表现:


image.png

图片分享:

Objective-C:

[[RSinaWeiboManager shared] shareImage:images text: text toStory: yesOrNo completion: completion];

Swift:

RSinaWeiboManager.shared.share(images: images, text: text, isToStory: trueOrFalse, completion: completion)

为统一接口, 图片用数组包装; 分享到「微博故事」功能中 text 字段会失效.

⚠️ 1. 开启「分享到微博故事」功能图片只能传一张; 多张图片分享的情况下「分享到微博故事」的功能必须关闭!
⚠️ 2. 图片单张不能超过 10 MB, 最多 9 张图片.

表现:
分享到微博:


image.png

分享到「微博故事」:

image.png

本地视频分享:

Objective-C:

[[RSinaWeiboManager shared] shareVideoWithLocalURL:videoFileURL text:text toStory:YesOrNo completion: completion];

Swift:

RSinaWeiboManager.shared.share(localVideoURL: videoFileURL, text: text, isToStory: trueOrFalse, completion: completion)

分享到「微博故事」功能中 text 字段会失效.

⚠️ 本地视频 URL 为通过 UIImagePickerController 选择的媒体 infoUIImagePickerControllerMediaURL 的值,形如: file:///private/var/mobile/Containers/Data/Application/3B368706-001D-4018-901B-284D64FA50E2/tmp/17BD98B4-A498-46E7-9715-6F39E73DFD75.MOV

表现:
分享到微博:


image.png

分享到「微博故事」:


image.png

网页分享:

Objective-C:

[[RSinaWeiboManager shared] shareWebpageWithURL: webpageURL objectID: @"id" title: title description: description thumbImage:thumbImage completion: completion];

Swift:

RSinaWeiboManager.shared.share(webpageURL: webpageURL, objectID: "id", title: title, description: description, thumbImage: thumbImage, completion: completion)

objectID 字段用于表示一个多媒体内容; 网页的消息体设置无需设置 delegate, 因为不涉及异步操作, 而且 thumbImage 字段应该是失效的, 即不显示缩略图.

⚠️ 1. 网页 thumbImage 字段的缩略图数据不能大于 32 Kb (新浪的 SDK 控制的很严格).
⚠️ 2. WBWebpageObjectdescription 设置无效, 想要显示网页的相关描述, 只能设置 WBMessageObjecttext 字段.

表现:


image.png

c. 返回本应用

Objective-C:

[[RSinaWeiboManager shared] application:app openURL:url options:options];

Swift:

RSinaWeiboManager.application(app, open: url, options : options)

Facebook

准备

分享需要注册平台, Facebook 开发者主页, Facebook SDK 支持 pod 集成, 分享接口调用说明.

集成

a. pod 集成: pod 'FBSDKLoginKit'
b. 在info.plist文件的CFBundleURLTypes中添加:

<key>CFBundleURLSchemes</key>
<array>
    <string>fbYOURAPPID</string>
</array>
<key>FacebookAppID</key>
<string>YOURAPPID</string>
<key>FacebookDisplayName</key>
<string>SOMENAME</string>

c. 添加以下至白名单:

<string>fbapi</string>
<string>fb-messenger-share-api</string>
<string>fbauth2</string>
<string>fbshareextension</string>

d. Swift 语言集成需要 Objective-C - Swift 桥接文件.

接口调用及内部实现

a. 初始化 SDK

Objective-C:

[[RFacebookManager shared] sdkInitializeByID:appID secret:secret];

Swift:

RFacebookManager.shared.sdkInitialize(appID: appID, secret: secret)

仅做分享功能的话, secret 字段无用.

b. 分享
参数的包装, 借助工具类 RFacebookHelper 来处理, Facebook 已经封装好了各个类型的分享内容载体 (FBSDKSharePhotoContent、FBSDKShareVideoContent ...), 对应设置属性值就可以; 对于图片的分享, 和新浪的图片分享思路相同, 即: 无论是单张还是多图, 一概通过数组包装传递给 RFacebookHelper, 然后它自行处理.

网页分享:
Objective-C:

[[RFacebookManager shared] shareWebpageWithURL: webpageURL
                           quote: quote
                         hashTag: hashTag
                            from: context
                            mode: mode
                      completion: completion];

Swift:

RFacebookManager.shared.share(webpageURL: webpageURL, quote: quote, hashTag: hashTag, from: context, mode: mode, completion: completion)

Facebook 的 SDK 在迭代的过程中, 舍弃了很多字段, 分享参数不如新浪那样多样, 但是其分享的表现形式却比国内的要友好很多, 特别是在网页分享这块体现的更加明显, 值得注意的是, 非网页形式的分享回调无效, 努力地在 GithubStack OverflowFacebook Developer 论坛找答案但都没有找到解决办法, 根据 postId 的判断分享结果状态的方法早已失效, 无论是 Android 还是 iOS 端, 目前都没办法.

⚠️ hashTag (话题) 的表现形式不同于国内, 新浪的话题格式: #话题#, 两个 # 之间一切内容都能成为话题, 而 Twitter、Instagram、Facebook 的格式: #话题, 话题内容词组之间不能有任何符号且必须连在一起.

表现:
客户端形式的分享(无回调):


image.png

由上图可见, 在通过客户端分享的过程中, quote 字段已经丢失, Android 表现不同, quote 保留, 其次, iOS 是跳转到 Facebook 客户端分享, Android 是在本应用内弹出对话框分享, 且 Android 通过客户端分享需要提前打开 Facebook 客户端, 否则无法弹出分享对话框, 测试过美图秀秀的图片分享, 也是一样, 需要提前打开客户端.

网页形式的分享(有回调):


image.png

图片分享:

Objective-C:

[[RFacebookManager shared] sharePhotos: targetImageArray
                    from:context
              completion: completion];

Swift:

RFacebookManager.shared.share(photos: targetImageArray, from: context, completion: completion)

⚠️ 1. 照片大小必须小于 12MB.
⚠️ 2. 用户需要安装版本 7.0 或以上的原生 iOS 版 Facebook 应用.

表现:


image.png

本地视频分享:

Objective-C:

[[RFacebookManager shared] shareVideoWithLocalURL: videoURL from: context];

Swift:

RFacebookManager.shared.share(localVideoURL: videoURL, from: context)

⚠️ 本地视频 URL 为通过 UIImagePickerController 选择的媒体 infoUIImagePickerControllerReferenceURL 的值, 形如: assets-library://asset/asset.MP4?id=8FF2F03F-DD84-41A5-A20C-B745E793C0DC&ext=MP4

倘若通过 Facebook 的 SDK 自行构建本地视频分享模型, 需注意 iOS 11 前后的模型属性设置不同:
Objective-C:

- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
  FBSDKShareVideo *video = [[FBSDKShareVideo alloc] init];
  if (@available(iOS 11, *)) {
    video.videoAsset = [info objectForKey:UIImagePickerControllerPHAsset];
  } else {
    video.videoURL = [info objectForKey:UIImagePickerControllerReferenceURL];
  }
  ...
}

Swift:

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
    let video = FBSDKShareVideo()
    if #available(iOS 11, *) {
        video.videoAsset = info[UIImagePickerControllerPHAsset]     
    } else {
        video.videoURL = info[UIImagePickerControllerReferenceURL]
    }
    ...
}

为了统一使用 URL 分享本地视频, 所以笔者并没有考虑使用上述的写法.

表现:


image.png

C. 返回本应用

Objective-C:

[[RFacebookManager shared]application:app openURL:url options:options];

Swift:

RFacebookManager.application(app, open: url, options : options)

d. 其他设置

在完成 Facebook 登录、分享等操作的时候还需要连接本应用的 AppDelegate , 故在 didFinishLaunchingWithOptions 函数中添加:

Objective-C:

[[RFacebookManager shared] application:application didFinishLaunchingWithOptions:launchOptions];

Swift:

RFacebookManager.shared.application(application, didFinishLaunchingWithOptions: launchOptions)

当需要记录有多少用户激活的时候需要在 applicationDidBecomeActive 方法中添加:

Objective-C:

[[RFacebookManager shared]applicationDidBecomeActive:application];

Swift:

RFacebookManager.shared.applicationDidBecomeActive(application)

Twitter

准备

分享需要注册平台, Twitter 开发者主页, 注册应用主页, Twitter SDK 支持 pod 集成, 分享接口调用说明.

⚠️: Twitter SDK 将于 2018/10/31 后不再进行维护, 但是不影响后续使用, 需自行维护, Twitter 产品经理 Neil Shah 对 Twitter SDK 放弃维护迭代的声明博客.

集成

a. pod 集成: pod 'TwitterKit'
b. 在 info.plist 文件的CFBundleURLTypes中添加:

<key>CFBundleURLSchemes</key>
<array>
    <string>twitterkit-YOURCONSUMERKEY</string>
</array>

c. 添加以下至白名单:

<string>twitter</string>
<string>twitterauth</string>

d. Swift 语言集成需要 Objective-C - Swift 桥接文件.

接口调用及内部实现

a. 初始化 SDK

Objective-C:

[[RTwitterManager shared] sdkInitializeByConsumerKey:yourConsumerKey consumerSecret:yourConsumerSecret];

Swift:

RTwitterManager.shared.sdkInitialize(consumerKey: consumerKey, consumerSecret: secret)

仅做分享的话, secret 字段无用.

b. 授权 Twitter 客户端
与其他平台分享不同的是, Twitter 在进行发推(分享)的时候会先进行检测本地的 SessionStore 的标记判断是否登录(授权)过, 所以在进行发推的时候需要进行这一步的判断, 在未登录的情况下需进行授权, 在此使用 RTwitterAuthHelper 进行处理, 登录(授权回调):

Objective-C:

typedef void (^auth)(RTWAuthState state, NSString* _Nullable errorInfo);

Swfit:

typealias RTWAuthCompletion = (_ state : RTWAuthState,_ errorInfo : String?) -> Void

state 包括成功和失败两种结果.

判断是否登录过:

Objective-C:

BOOL flag = [[RTwitterAuthHepler shared] hasLogged];

Swift:

let _ = RTwitterAuthHepler.shared.hasLogged

登录授权:

Objective-C:

[[RTwitterAuthHelper shared]authorizeTwitter:^(RTWAuthState state, NSString * _Nullable errorInfo) {
    // some code ...
}];

Swift:

RTwitterAuthHepler.shared.authorizeTwitter { (state, errorInfo) in
    // some code ...
}

这一步目前的情况就是把 Twitter SDK 提供的授权方法重新包装写了遍, 但是考虑到未来可能用到 sessiontoken 等信息并处理, 所以单独写了类讲授权和分享隔离.

返回本应用:

Twitter 分享是不需要进行程序跳转的, 只有在登录授权的时候才会需要下述方法, 所以当你成功授权了以后卸载掉 Twitter 的客户端依然可以进行分享.

Objective-C:

[[RTwitterManager shared] application:app openURL:url options:options];

Swift:

RTwitterManager.application(app, open: url, options : options)

⚠️ Twitter 最新 SDK 要求项目的 Deloyment Target 至少为 9.0.

c. 分享

在本人写的 demo 中, 分享登录授权是衔接的, 即: 若未登录过 -> 登录授权 -> 分享.
另, Twitter 能分享的内容相对较少, 所以关于文字、网页、图片的分享, 统一到一个分享接口里, 三者不能同时为空.

Objective-C:

[[RTwitterManager shared]shareWithWebpageURL: webpageURL text: text image: image from: context completion: completion];

Swift:

RTwitterManager.shared.share(webpageURL: webpageURL, text: text, image: image, from: context, completion: completion)

表现:


image.png

Twitter 分享是在本应用内弹出分享框进行分享.

Instagram

准备

分享无需注册平台无需 SDK, Instagram 开发者主页, Custom URL Scheme 方式分享.

配置

info.plist文件中:
添加以下至白名单:

<string>instagram</string>

接口调用及内部实现

分享

无论照片还是视频都是通过 Custom URL Scheme 来打开 Instagram 客户端, 但 demo 中实际的方法和 Instagram 提供的 Custom URL不同.

Objective-C:

static NSURL* instagramLibraryURL() {
    NSString *str = [NSString stringWithFormat:@"instagram://library?AssetPath=%@", @""];
    return [NSURL URLWithString:str];
}

Swift:

fileprivate let instagramURL = URL(string: String(format: "instagram://library?AssetPath=%@", "" as CVarArg))

分享流程是先保存照片/视频再分享.
保存照片:

Objective-C

UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), (__bridge void *)self);
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
    if ([[UIApplication sharedApplication] canOpenURL:instagramLibraryURL()]) {
        [[UIApplication sharedApplication] openURL:instagramLibraryURL()];
    }
}

Swift:

UISaveVideoAtPathToSavedPhotosAlbum(localVideoURL.path, self, #selector(video(path:didFinishSavingWithError:contextInfo:)), nil)
@objc fileprivate func image(image: UIImage!, didFinishSavingWithError error: NSError!, contextInfo: AnyObject!) {
        if UIApplication.shared.canOpenURL(instagramURL!) {
            UIApplication.shared.openURL(instagramURL!)
        }
        
}

保存视频:

Objective-C:

UISaveVideoAtPathToSavedPhotosAlbum(localeVideoURL.path, self, @selector(video:didFinishSavingWithError:contextInfo:), nil);
- (void)video:(NSString *)path didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
    
    if ([[UIApplication sharedApplication] canOpenURL:instagramLibraryURL()]) {
        [[UIApplication sharedApplication] openURL:instagramLibraryURL()];
    }
}

Swift:

UISaveVideoAtPathToSavedPhotosAlbum(localVideoURL.path, self, #selector(video(path:didFinishSavingWithError:contextInfo:)), nil)
@objc fileprivate func video(path: String!, didFinishSavingWithError error: NSError!, contextInfo: AnyObject!) {
    if UIApplication.shared.canOpenURL(instagramURL!) {
        UIApplication.shared.openURL(instagramURL!)
    }
}

分享图片:

Objective-C:

[[RInstagramManager shared] share: targetImage];

Swift:

RInstagramManager.shared.share(image: targetImage)

表现:


image.png

分享本地视频:

Objective-C:

[[RInstagramManager shared]shareVideoWithLocalURL: videoURL description: description]

Swift:

RInstagramManager.shared.share(localVideoURL: videoURL, description: description)

description 字段在实际传递过程中是失效的.

表现:


image.png

⚠️ 本地视频 URL 为通过 UIImagePickerController 选择的媒体 infoUIImagePickerControllerMediaURL 的值,形如: file:///private/var/mobile/Containers/Data/Application/3B368706-001D-4018-901B-284D64FA50E2/tmp/17BD98B4-A498-46E7-9715-6F39E73DFD75.MOV

Tumblr

准备

分享需要注册平台, Tumblr 开发者主页, 注册应用主页, Tumblr SDK 支持 pod 集成, 分享接口调用说明.

集成

a. pod 集成: pod 'Flurry-iOS-SDK/TumblrAPI'
⚠️: 一定是这个, 最新版本的 SDK 我没有找到分享的接口.

b. Swift 语言集成需要 Objective-C - Swift 桥接文件.

接口调用及内部实现

a. 初始化 SDK

Objective-C:

[[RTumblrManager shared] sdkInitializeByConsumerKey:yourConsumerKey consumerSecret: yourConsumerSecret];

Swift:

RTumblrManager.shared.sdkInitialize(consumerKey: yourConsumerKey, consumerSecret: yourConsumerSecret)

⚠️ 在 iOS 端初始化 SDK 需要 consumerKeyconsumerSecret 两个参数, Android 端还需要 flurryKey 这个参数才能完成分享.

b. 分享

Tumblr 分享体只包括图片文字两种, 并且图片还是网络图片的链接, 并不能分享本地图片, 所以 Tumblr 的局限性很大, 这两种分享体的模型通过 SDK 中 FlurryImageShareParametersFlurryTextShareParameters 来构建;
Tumblr 分享是通过当前界面弹出对话框分享的, 和 Twitter 类似, 所以不需要判断 Tumblr 程序是否安装;
Tumblr 的分享流程是: 登录 -> 分享, 但是登录的逻辑不需要在代码中实现, 他会自动呈现浏览器的登录界面.

文字分享:

Objective-C:

[[RTumblrManager shared] shareText: text title: title webpageURL: webpageURL from: context completion: completion];

Swift:

RTumblrManager.shared.share(text: text, title: title, webpageURL: webpageURL, from: context, completion: completion)

表现:


image.png

图片链接分享:

Objective-C:

[[RTumblrManager shared] shareImageWithURL: targetImageURL description: description webpageURL: webpageURL from: context completion: completion];

Swift:

RTumblrManager.shared.share(imageURL: targetImageURL, description: description, webpageURL: webpageURL, from: context, completion: completion)

表现:


image.png

Pinterest

准备

分享需要注册平台, Pinterest 开发者主页, 注册应用主页, Pinterest SDK 支持 pod 集成, 接口调用说明.

集成

a. pod 集成: pod “PinterestSDK”, :git => “git@github.com:pinterest/ios-pdk.git”

d. 在 info.plist 文件的 CFBundleURLTypes 中添加:

<key>CFBundleURLTypes</key>
  <array>
    <dict>
      <key>CFBundleURLName</key>
      <string></string>
      <key>CFBundleURLSchemes</key>
      <array>
        <string>pdkYOURAPPID</string>
      </array>
    </dict>
  </array>

e. 添加以下至白名单:

<string>pinterestsdk.v1</string>

b. Swift 语言集成需要 Objective-C - Swift 桥接文件.

接口调用及内部实现

a. 初始化 SDK

Objective-C:

[[RPinterestManager shared] sdkInitializeByAppID: yourAppID appSecret:yourAppSecret];

Swift:

RPinterestManager.shared.sdkInitialize(appID: yourAppID, appSecret: yourAppSecret)

仅做分享功能的话, secret 字段无用.

b. 分享

⚠️: Pinterest 分享要求项目的 CFBundleDisplayName 一定不能为空!!!

图片链接分享:

Objective-C:

[[RPinterestManager shared] shareImageWithURL: targetImageURL webpageURL: webpageURL onBoard:boardName description: description  from: context completion:completion];

Swift:

RPinterestManager.shared.share(imageURL: targetImageURL, webpageURL: webpageURL, boardName: boardName, description: description, from: context, completion: completion)

boardName 字段即使随便设置也不影响, 考虑到有可能对 Pinterest 功能细化的时候会用到这个字段就保留下来了.

表现:


image.png

c. 返回本应用

Objective-C:

[[RPinterestManager shared] application:app openURL:url options:nil];

Swift:

RPinterestManager.application(app, open: url, options : options)

Line

准备

分享无需注册平台.

配置

info.plist文件中:
添加以下至白名单:

<string>line</string>

接口调用及内部实现

分享

Line 分享是通过 Custom URL Scheme 来打开 Line 客户端进行分享, 但 demo 中实际的方法和 Line 提供的 Custom URL.

文本分享的 URL 为 line://msg/text/?targetText, 要对 targetText 中的中文、特殊字符等进行处理:

Object-C:

[targetText stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];

Swift:

targetText.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)

图片分享的 URL 为 line://msg/image/, URL 后面的部分可通过 UIPasteboard 实例的 name 属性拼接:

Objective-C:

UIPasteboard* p = [UIPasteboard generalPasteboard];
[p setData:(UIImageJPEGRepresentation(image, 1)) forPasteboardType:@"public.jpeg"];
NSURL* lineURL = [NSURL URLWithString:[NSString stringWithFormat:@"line://msg/image/%@",p.name]];

Swift:

let p = UIPasteboard.general
p.setData(UIImageJPEGRepresentation(image, 0.1)!, forPasteboardType:"public.jpeg")
let lineURL = URL(string: String(format: lineURLPrefix + "line://msg/image/%@", p.name as CVarArg))

文字分享:

Objective-C:

[[RLineManager shared] shareText: text];

Swift:

RLineManager.shared.share(text: text)

表现:


image.png

图片分享:

Objective-C:

[[RLineManager shared] shareImage: targetImage];

Swift:

RLineManager.shared.share(image: targetImage)

表现:

image.png

WhatsApp

准备

分享无需注册平台.

配置

info.plist文件中:
添加以下至白名单:

<string>whatsapp</string

接口调用及内部实现

分享
通过 Custom URL Scheme 分享文字, 图片是通过 UIDocumentInteractionController 来实现应用间数据共享, 构建文字的 URL 为 whatsapp://send?text=targetText

文字分享:

Objective-C:

[[RWhatsAppManager shared]shareText: text]

Swift:

RWhatsAppManager.shared.share(text: text)

表现:


image.png

图片分享:

Objective-C:

[[RWhatsAppManager shared]shareImage: targetImage from: context];

Swift:

RWhatsAppManager.shared.share(image: targetImage , from: context)

表现:


image.png

GooglePlus

准备

分享无需注册平台, Google Plus 开发者主页已经把 iOS 相关移除了.

接口调用及内部实现

分享
Google Plus 只支持通过 Custom URL Scheme 分享网页, 构建 URL 为 https://plus.google.com/share

内部构建:

Objective-C:

NSURLComponents* urlComponents = [[NSURLComponents alloc]
                                      initWithString:@"https://plus.google.com/share"];
urlComponents.queryItems = @[[[NSURLQueryItem alloc]
                                  initWithName:@"url"
                                  value:[shareURL absoluteString]]];
NSURL* url = [urlComponents URL];    
if ([SFSafariViewController class]) {
    SFSafariViewController* controller = [[SFSafariViewController alloc] initWithURL:url];
    controller.delegate = self;
    [from presentViewController:controller animated:YES completion:nil];
} else {
    [[UIApplication sharedApplication] openURL:url];
}

Swfit:

var components = URLComponents(string: "https://plus.google.com/share")
components?.queryItems = [URLQueryItem(name: "url", value: webpageURL.absoluteString)]
let url = components?.url
if #available(iOS 9, *) {
    let vc = SFSafariViewController(url: url!)
    vc.delegate = self
    from.present(vc, animated: true, completion: nil)
} else {
    UIApplication.shared.openURL(url!)
}

网页分享:
Objective-C:

[[RGooglePlusManager shared]shareURL:[NSURL URLWithString: targetURL] from: context];

Swift:

RGooglePlusManager.shared.share(webpageURL: URL(string: targetURL)!, from: context)

表现:


image.png

统一分享接口

缺陷

缺陷说在前面, 其实本来不打算统一接口的.

  • 假如只想分享某五个平台, 其余的六个平台仍然不可以删掉, 主分享 Manager 和子平台分享 Manager 存在耦和;
  • 分享接口优化受限制, 由于前面的平台分享对比表格可知, 国外的平台分享很多都没有回调, 而国内的平台分享内容又存在多种形式, 无法实现高度统一;
  • 添加平台没有做去重处理, 造成不必要的开销;
  • 分享完毕返回到本应用的统一处理中子平台分享 Manager 和主分享 Manager 存在代码污染.

类图

iOS 分享统一接口类图.png
  • RShareManger: 主分享 Manager, 子平台 Manager 的初始化、分享、应用跳转和一些其他操作都在此进行;
  • RPlatform: 主要进行应用是否安装、添加目标应用的操作;
  • RRegister: 主要进行 RShareManager 和子平台分享 Manager 的 SDK 初始化衔接;
  • RImageContent、RVideoContent、RTextContent、RWebpageContent 为四种对应分享内容模型.

详细设计

**a. 平台添加 **

平台相关都交给 RPlatform 去处理, 平台的添加借鉴了 Java 中的 Builder 模式思路去处理, RPlatform 的成员属性 targets 在 Objective-C 中为:

@property (strong, nonatomic, readonly) NSArray<Class>* targets;

Swift 中为:

var targets : Array<RShare.Type> = [] // 多态的体现

add 函数为添加平台的操作, 参数为平台枚举 RShareSDKPlatform, RPlatform 的私有成员属性 info 为字典类型, key 为平台的字符串形式, value 平台类型 , 通过 add 操作的平台枚举去取出 info 中对应的平台类型添加到 targets 中.

b. 分享频道

以 Objective-C 为例, 在 RShareManager 定义了分享通道枚举:

typedef NS_ENUM(NSInteger, RShareChannel) {
    RShareChannelQQSession, // QQ 好友
    RShareChannelQQFavorite, // QQ 收藏
    RShareChannelQQDataLine, // QQ 我的电脑(数据传输)
    RShareChannelQZone, // QQ 空间
    RShareChannelWechatSession, // 微信好友
    RShareChannelWechatFavorite, // 微信收藏
    RShareChannelWechatTimeline, // 微信朋友圈
    RShareChannelFacebookClient, // Facebook 客户端
    RShareChannelFacebookBroswer, // Facebook Feed 形式网页
    RShareChannelTwitter,// 推特
    RShareChannelSinaWeibo, // 新浪微博
    RShareChannelSinaWeiboStory, // 新浪微博 - 我的故事
    RShareChannelLine, // Line
    RShareChannelInstagram, // Instagram
    RShareChannelTumblr, // Tumblr
    RShareChannelPinterest, // Pinterest
    RShareChannelGooglePlus, // GooglePlus
    RShareChannelWhatsApp // WhatsApp
};

c. 初始化以及注册平台

由 ShareSDK iOS 版激发灵感进行构建.

RShareManager 通过单例创建, 实例函数 registerPlatforms: 为注册实例化子平台 Manager 的过程, Objective-C 中通过 runtime 实现:

// code snippet ...
@autoreleasepool {
    Class cls = p.targets[i];
    id obj = objc_msgSend(objc_msgSend(cls, @selector(alloc)), @selector(init));
    SEL sel = @selector(connect:);
    ((void(*)(id,SEL, RConfiguration))objc_msgSend)(obj, sel, c);
}

Swift 中暂时没找到类似的方法, 采用了原始的 switch-case 逐个判断并初始化.
其中 RConfiguration 为:

Objective-C:

// 定义在 RShare.h 中
typedef void (^RConfiguration)(RShareSDKPlatform platform, RRegister* obj); 

Swift:

// 定义在 RShareManager 中
typealias RConfiguration = (_ paltform : RShareSDKPlatform,_ obj : RRegister) -> Void

每个子平台 Manager 都有 connect 函数, 参数是 RConfiguration 类型, 返回平台信息是因为不是所有的平台都需要初始化 SDK, 返回 RRegister 实例的目的是把实例化的工作交给 RRegister 去做.

RRegister 内部逻辑很简单, 只有初始化三方平台 SDK 的工作.

d. 返回本应用

Objective-C 中通过 runtime 实现:

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options { 
    SEL sel = @selector(application:openURL:options:);
    id obj = objc_msgSend(objc_msgSend(_cls, @selector(alloc)), @selector(init));
    return ((BOOL(*)(id,SEL,id,id,id))objc_msgSend)(obj, sel, application,url,options);
}

其中 _cls 是在分享的时候通过分享接口中 channel 字段通过 -(Class)getCls:(RShareChannel)channel 确定, objCls 在 Swift 中通过同样的方法获得: func getSubCls(channel : RShareChannel) -> RShare.Type.

func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
    return objCls.application(app, open: url, options : options)     
}

接口

添加平台及初始化需要注册的平台:

Objective-C:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    RPlatform* p = [RPlatform make:^(PlatformBuilder *builder) {
        [builder add:RShareSDKPinterest];
        [builder add:RShareSDKWhatsApp];
        [builder add:RShareSDKWechat];
        [builder add:RShareSDKSina];
        [builder add:RShareSDKQQ];
        [builder add:RShareSDKTumblr];
        [builder add:RShareSDKFacebook];
        [builder add:RShareSDKTwitter];
        [builder add:RShareSDKLine];
        [builder add:RShareSDKGooglePlus];
        [builder add:RShareSDKInstagram];
    }];
    
    [[RShareManager shared] registerPlatforms:p onConfiguration:^(RShareSDKPlatform platform, RRegister *obj) {
        switch (platform) {
            case RShareSDKPinterest:
                [obj connectPinterestByAppID: yourAppID appSecret: nil];
                break;
            case RShareSDKQQ:
                [obj connectQQByAppID:yourAppID appKey: yourKey];
                break;
                
            case RShareSDKSina:
                [obj connectSinaWeiboByAppKey: yourKey  appSecret:yourSecret];
                break;
            case RShareSDKWechat:
                [obj connectWechatByAppID: yourAppID appSecret:yourSecret];
                break;
            case RShareSDKTumblr:
                [obj conncetTumblrByConsumerKey: yourKey  consumerSecret: yourSecret];
                break;
            case RShareSDKFacebook:
                [obj connectFacebookByID:yourAppID secret:nil];
                break;
            case RShareSDKTwitter:
                [obj connectTwitterByConsumerKey:yourKey consumerSecret:yourSecret];
                
            default:
                break;
        }
    }];
    
    return YES;
}

Swift:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    
    let platform = RPlatform.make { (builder) in
        builder.add(p: .Facebook)
        builder.add(p: .Twitter)
        builder.add(p: .QQ)
        builder.add(p: .Wechat)
        builder.add(p: .Instagram)
        builder.add(p: .Tumblr)
        builder.add(p: .Pinterest)
        builder.add(p: .Sina)
        builder.add(p: .GooglePlus)
        builder.add(p: .Line)
        builder.add(p: .WhatsApp)       
            
    }
    RShareManager.shared.registerPlatform(platform: platform) { (p, obj) in
        switch p {
            case .Facebook:
                obj.connectFacebook(appID: yourAppID, secret: nil)
            case .Pinterest:
                obj.connectPinterest(appID: yourAppID, secret: nil)
            case .QQ:
                obj.connectQQ(appID: yourAppID, key: yourKey)
            case .Sina:
                obj.connectSinaWeibo(appKey: yourKey, secret: yourSecret)
            case .Wechat:
                obj.connectWechat(appID: yourAppID, secret: yourSecret)
            case .Tumblr:
                obj.connectTumblr(consumerKey: yourKey, secret: yourSecret)
            case .Twitter:
                obj.connectTwitter(consumerKey: yourKey, secret: yourSecret)
            default : break
        }
    }

        return true
}

构建分享模型:

RImageContent 为例:

Objective-C:

RImageContent* content = RImageContent make:^(RImageContentBuilder *builder) {
     // ...   
}

Swift:

RImageContent.make { (builder) in
    // ...    
}

分享:

以分享 RImageContent 为例:

Objective-C:

[RShareManager shared] shareImageWithContent:content channel: channel from: context completion:^(RShareSDKPlatform platform, ShareResult result, NSString * _Nullable errorInfo) {
     // ...   
}

Swift:

RShareManager.shared.shareImage(content: content, channel: channel, from: context) { (platform, result, errorInfo in
    // ...    
}   

返回本应用:

Objective-C:

[[RShareManager shared]application:app openURL:url options:options];

Swift:

RShareManager.shared.application(app, open: url, options : options)   

源码

Objective-C 版本源码Swift 版本源码.

无关

个人博客, 还在学习中, 请多指教.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容