文章涉及的demo在Github LQThirdParty, 欢迎Star | Fork
关于第三方登录/分享的接入, 很多时候使用的是友盟或者ShareSDK; 但并不是每次都想使用这些第三方的服务的, 这里作者整理了微信, QQ, 新浪微博原生第三方的接入:
[Swift]原生第三方接入: 微信篇--集成/登录/分享/支付
[Swift]原生第三方接入: QQ篇--集成/登录/分享
[Swift]原生第三方接入: 新浪微博篇--集成/登录/分享
一. 集成
1.1 新建应用
首先, 想要使用腾讯相关的功能, 您必须注册成为腾讯认证的开发者, 并且在腾讯开放平台创建了应用, 即已经获取到了相应的 ** APPID ** 和 ** APPKEY **.
1.2. 集成SDK
个人感觉腾讯的文档不太友好, 先给出官方文档地址和官方SDK下载地址下载最新的文档及SDK. 将下载后的SDK中的 TencentOpenApi_IOS_Bundle.bundle 和 TencentOpenAPI.framework,添加到项目工程目录.
添加系统依赖库
到Build Phases -> Link Binary With Libraries, 添加以下系统库 :
- Security.framework
- SystemConfiguration.framework
- CoreGraphics.Framework
- CoreTelephony.framework
- libiconv.dylib
- libsqlite3.dylib
- libstdc++.dylib
- libz.dylib
后面四个下新版Xcode中为: - libiconv.tbd
- libsqlite3.tbd
- libstdc++.tbd
- libz.tbd
添加 TencentOpenApi_IOS_Bundle.bundle
然后来到Build Phases -> Copy Bundle Resources
将 TencentOpenApi_IOS_Bundle.bundle 添加进来(一般会自动添加到这里, 看下有没有即可, 没有的话, 点击 + 添加)
添加 -fobjc-arc
来到Build Settings -> Other Linker Flags
添加 -fobjc-arc
PS: 如果这里已有其他内容, 加个空格粘贴进去即可, 个人尝试可行.
添加URL Scheme
来到Info-> URL Types, 点击左下角的 + 新加一个Scheme
格式: tencent+AppID
例如你的AppID为: 123456789
则你的Scheme为: tencent123456789
适配iOS 9+ , 添加Scheme白名单
- ** 方式一 **
在Info.plist文件内新加字段: LSApplicationQueriesSchemes, 类型为Array(数组)
然后添加内容, 类型为String(字符串)
QQ需要添加以下字段:
- mqqOpensdkSSoLogin,
- mqqopensdkapiV2,
- mqqopensdkapiV3,
- wtloginmqq2,
- mqq,
- mqqapi
- mqqopensdkdataline
QZONE 需要添加:
- mqzoneopensdk,
- mqzoneopensdkapi,
- mqzoneopensdkapi19,
- mqzoneopensdkapiV2,
- mqqOpensdkSSoLogin,
- mqqopensdkapiV2,
- mqqopensdkapiV3,
- wtloginmqq2,
- mqqapi,
- mqqwpa,
- mqzone,
- mqq
- mqqopensdkapiV4
- mqqopensdkdataline
如果同时需要QQ和Qzone, 只需要添加Qzone即可;
- ** 方式二 **
或者, 在Info.plist文件右键, Open as... -> Source Code, 可打开文件, 进行编辑, 在倒数第三行(即: 标签的上面)的空白处添加以下代码:
LSApplicationQueriesSchemes
mqzoneopensdk
mqzoneopensdkapi
mqzoneopensdkapi19
mqzoneopensdkapiV2
mqqOpensdkSSoLogin
mqqopensdkapiV2
mqqopensdkapiV3
wtloginmqq2
mqqapi
mqqwpa
mqzone
mqq
mqqopensdkapiV4
mqqopensdkdataline
如果还有其他平台的白名单需要添加, 例如微信, 新浪微博, 只需要在
PS: Info.plist文件显示为Source Code后, 如果还想显示原来的列表格式, 可以: 邮件 -> Open as.. -> Property List 即可.
适配iOS 9+, 网络请求
- **方式一: 暂时回退到HTTP请求 **
在Info.plist文件中添加字段: ** NSAppTransportSecurity , 类型为字典;
然后添加一个Key: NSAllowsArbitraryLoads **, 类型为Boolean, 值为 YES
或者以Source Code 打开Info.plist文件, 空白处添加以下代码:
NSAppTransportSecurity
NSAllowsArbitraryLoads
- ** 方式二: 设置域 **
在项目的info.plist中添加一个Key:NSAppTransportSecurity,类型为字典类型。
然后给它添加一个值: NSExceptionDomains,类型为字典类型;
把需要的支持的域添加給NSExceptionDomains
其中域作为Key,类型为字典类型。
每个域下面需要设置3个属性:
NSIncludesSubdomains、
NSExceptionRequiresForwardSecrecy、NSExceptionAllowsInsecureHTTPLoads。
均为Boolean类型,值分别为YES、NO、YES
QQ需要设置的域为:
- qq.com
或者以Source Code 打开Info.plist文件, 空白处添加以下代码:
NSAppTransportSecurity
NSExceptionDomains
qq.com
NSExceptionAllowsInsecureHTTPLoads
NSExceptionRequiresForwardSecrecy
NSIncludesSubdomains
PS: 这种方式需要对每个要以HTTP方式访问的域名进行设置, 比较麻烦, 建议使用第一种方式.
到此, 集成及适配结束...
PS: 在使用相关API的时候, 需要新建桥接头文件, 或者在已有桥接头文件内引用其头文件:
#import
#import
#import
二. 登录
在AppDelegate.swift中注册app:
QQ在注册App的时候, 和其他的不同, 是要创建一个TencentOAuth对象, 来发起授权申请:
var tencentAuth: TencentOAuth!
self. tencentAuth = TencentOAuth(appId: qqAppID, andDelegate: self)
在方法 func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool 中添加回调:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
let urlKey: String = options[UIApplicationOpenURLOptionsKey.sourceApplication] as! String
if urlKey == "com.tencent.mqq" {
// QQ 的回调
return TencentOAuth.handleOpen(url)
}
return true
}
然后, 实现其代理方法:
func tencentDidLogin() {
// 登录成功后要调用一下这个方法, 才能获取到个人信息
self.tencentAuth.getUserInfo()
}
func tencentDidNotNetWork() {
// 网络异常
}
func tencentDidNotLogin(_ cancelled: Bool) {
}
func getUserInfoResponse(_ response: APIResponse!) {
// 获取个人信息
if response.retCode == 0 {
if let res = response.jsonResponse {
if let uid = self.tencentAuth.getUserOpenID() {
// 获取uid
}
if let name = res["nickname"] {
// 获取nickname
}
if let sex = res["gender"] {
// 获取性别
}
if let img = res["figureurl_qq_2"] {
// 获取头像
}
}
} else {
// 获取授权信息异常
}
}
最后在需要发起登录的地方添加以下代码来发起登录申请:
let appDel = UIApplication.shared.delegate as! AppDelegate
// 需要获取的用户信息
let permissions = [kOPEN_PERMISSION_GET_USER_INFO, kOPEN_PERMISSION_GET_SIMPLE_USER_INFO]
appDel.tencentAuth.authorize(permissions)
到此, 一个完整的QQ登录授权流程就完成了,并成功的获取到了QQ用户的相关信息, 即登录成功.
上面我是在AppDelegate.swift方法内处理的回调, 我们可以完全写在其他的类里面, 只需要将其代理对象设置为需要处理回调的对象即可.
三. 分享
一直在吐槽腾讯的开放平台, 找个文档真心不容易, 绕了一些弯路, 最后还是百度搜到了文档地址, 确实很尴尬. 虽然找到了官方文档说明, 但是不是特别详细, 可以下载其详细文档来参考设置.
PS: 在配置工程的时候需要注意, 如果需要兼容旧版本的手机QQ, 需要额外添加一个 ** URL Scheme ** : QQ + 十六进制的AppID ,不足八位的在首部补0 ; 例如你的AppID为: 123456 , 其十六进制为: 1E240, 则, URL Scheme 为: QQ0001E240
不过, 现在的QQ版本都比较高了, 至于低于哪个版本需要兼容, 官方文档没有提, 如果你的开发中适配低版本QQ时不能成功分享, 不妨添加试试.
如果是单独集成了分享, 在注册APP的时候依然是使用下面这个方法:
TencentOAuth(appId: "appid", andDelegate: nil)
如果没有登录, 这里代理传nil即可; 如果有登录, 按登录的设置即可;
在其代理类中实现代理( QQApiInterfaceDelegate )方法:
func onReq(_ req: QQBaseReq!) {
}
func onResp(_ resp: QQBaseResp!) {
if resp is SendMessageToQQResp {
let rs = resp as! SendMessageToQQResp
if rs.type == 2 {
// QQ分享返回的回调
if rs.result == "0" {
// 分享成功
print("分享成功")
} else {
print("分享失败")
}
}
}
}
func isOnlineResponse(_ response: [AnyHashable : Any]!) {
}
虽然我们只用在代理方法 ** func onResp(_ resp: QQBaseResp!) ** 中处理分享的回调, 但其他两个方法也是要实现.
PS: 这里需要注意, 如果项目中集成了微信, QQ的代理和微信的代理需要分开处理, 不能使用同一个对象, 否则会编译报错:
Method 'onReq' with Objective-C selector 'onReq:' conflicts with previous declaration with the same Objective-C selector
然后在 ** func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool ** 方法中添加回调:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
return QQApiInterface.handleOpen(url, delegate: LDShareUnit.shared)
}
最后, 就是在需要分享的地方发起分享了, 在这里我遇到一个坑.
分享文本
以分享文本为例. 开始我的代码是这样的:
let textObj = QQApiTextObject()
textObj.text = "这是分享到QQ的一段文字"
textObj.title = "这是分享到QQ的标题"
textObj.description = "一段描述"
textObj.shareDestType = ShareDestTypeQQ // 分享到QQ 还是TIM, 必须指定
let req = SendMessageToQQReq()
req.message = textObj
let code = QQApiInterface.send(req)
print(code)
这样写, 一直无法吊起QQ客户端, 打印返回的code值, 一直是: ** QQApiSendResultCode(rawValue: -1)**, 发送失败; 查了很多资料, 尝试了各种情况, 都没能解决. 最后搁置了些时间, 一时心血来潮, 换了个初始化的方法, 改成如下这样:
let textObj = QQApiTextObject(text: "这是分享到QQ的一段文字")
// textObj.text = "这是分享到QQ的一段文字"
textObj?.title = "这是分享到QQ的标题"
textObj?.description = "一段描述"
textObj?.shareDestType = ShareDestTypeQQ // 分享到QQ 还是TIM, 必须指定
let req = SendMessageToQQReq(content: textObj)
req?.message = textObj
// QQApiInterface.openQQ()
let code = QQApiInterface.send(req)
print(code)
奇迹竟然发生了, 就这样, 成功的吊起QQ客户端, 并分享出去了.......
上面的title和description不需要设置, 设置了也看不到.
关于 shareDestType 参数, 官方文档指出必须设置, 否则无法正常工作, 我发现即使不设置, 默认也是吊起QQ发起分享的.
这算是一个比较大, 也是比较坑的一个大坑了, 花了很多时间, 做了一些无用功, 这也算是其API的一个不太友好的地方吧.
PS: 这里对 QQApiObject 的一个参数 cflag 做一下说明, 这是设置要分享的内容到什么地方, 空间, 收藏, 聊天, 电脑等
// QQApiObject control flags
enum
{
kQQAPICtrlFlagQZoneShareOnStart = 0x01,
kQQAPICtrlFlagQZoneShareForbid = 0x02,
kQQAPICtrlFlagQQShare = 0x04,
kQQAPICtrlFlagQQShareFavorites = 0x08, //收藏
kQQAPICtrlFlagQQShareDataline = 0x10, //数据线
};
- kQQAPICtrlFlagQQShareDataline
只分享文字/图片等到电脑 - kQQAPICtrlFlagQQShareFavorites
只分享到文字/图片等到收藏 - kQQAPICtrlFlagQQShare
分享到QQ, 可以选择到联系人/ 收藏/ 电脑/ 空间等 - kQQAPICtrlFlagQZoneShareForbid
禁止分享到QQ空间 - kQQAPICtrlFlagQZoneShareOnStart
只分享到QQ空间
如果想要设置不同的分享目标, 可以设置这个参数.
分享图片
- 单图
func shareImageToQQ() {
// 原图 最大5M
let img = UIImage(named: "1.jpg")
let data = UIImageJPEGRepresentation(img!, 0.8)
// 预览图 最大 1M
let thumb = UIImage(named: "qq")
let thData = UIImagePNGRepresentation(thumb!)
let imgObj = QQApiImageObject(data: data, previewImageData: thData, title: "分享的一张图片", description: "分享图片的描述")
let req = SendMessageToQQReq(content: imgObj)
// 分享到QQ
QQApiInterface.send(req)
// 分享到Qzone
// QQApiInterface.sendReq(toQZone: req)
}
如果要分享到QZone, 可以设置imgObj?.cflag = UInt64(kQQAPICtrlFlagQZoneShareOnStart), 也可以使用QQApiInterface.sendReq(toQZone: req)
- 多图
多图只能分享到 QQ收藏
func shareImagesToQQ() {
// 多图不支持分享到QQ, 如果设置, 默认分享第一张
// k可以分享多图到QQ收藏
let img = UIImage(named: "1.jpg")
let data = UIImageJPEGRepresentation(img!, 0.8)
let img1 = UIImage(named: "10633861_160536558132_2.jpg")
let data1 = UIImageJPEGRepresentation(img1!, 0.8)
let thumb = UIImage(named: "qq")
let thData = UIImagePNGRepresentation(thumb!)
let imgObj = QQApiImageObject(data: data, previewImageData: thData, title: "分享多个图片", description: "描述", imageDataArray: [data!, data1!])
// 设置分享目标为QQ收藏
imgObj?.cflag = UInt64(kQQAPICtrlFlagQQShareFavorites)
let req = SendMessageToQQReq(content: imgObj)
QQApiInterface.send(req)
}
PS: 这里在设置分享到QQ收藏的时候, 遇到一个错误提示:
-canOpenURL: failed for URL: "mqqopensdkdataline://" - error: "This app is not allowed to query for scheme mqqopensdkdataline"
这是因为之前配置的工程白名单的时候没有添加** mqqopensdkdataline** , 在第一部分的 适配iOS 9+ , 添加Scheme白名单 中添加** mqqopensdkdataline**即可;
分享新闻链接
新闻可以分享到QQ, 也可以只分享到QZone
func shareNewsToQQ() {
let url = URL(string: "http://www.jianshu.com/u/2846c3d3a974")
let preURL = URL(string: "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1496830989997&di=5ca7528be6f496c6500a436c8775a67d&imgtype=0&src=http%3A%2F%2Fwww.pp3.cn%2Fuploads%2F201502%2F2015021111.jpg")
let obj = QQApiNewsObject(url: url!, title: "关注作者流火绯瞳", description: "这是一个coder, 不是一个美女", previewImageURL: preURL!, targetContentType: QQApiURLTargetTypeNews)
let req = SendMessageToQQReq(content: obj)
// 分享到QQ
// QQApiInterface.send(req)
// 分享到QZone
QQApiInterface.sendReq(toQZone: req)
}
在创建新闻实例对象 QQApiNewsObject 的时候, 可以使用上面的方法, 也可以使用:
QQApiNewsObject(url: URL!, title: String!, description: String!, previewImageData: Data!, targetContentType: QQApiURLTargetType)
只是参数类型不同, 含义和上面的方法是一致的;
分享音乐
音乐可以分享到QQ和QZone
func shareMusicToQQ() {
let url = URL(string: "http://y.qq.com/i/song.html?songid=432451&source=mobileQQ%23wechat_redirect")
let preUrl = URL(string: "http://imgcache.qq.com/music/photo/mid_album_300/V/E/000J1pJ50cDCVE.jpg")
let obj = QQApiAudioObject(url: url!, title: "歌曲名:不要说话", description: "专辑名:不想放手歌手名:陈奕迅", previewImageURL: preUrl!, targetContentType: QQApiURLTargetTypeVideo)
let req = SendMessageToQQReq(content: obj)
// 分享到QQ
// QQApiInterface.send(req)
// 分享到QZone
QQApiInterface.sendReq(toQZone: req)
}
这里在创建 QQApiAudioObject 实例对象的时候, 可以使用上面的方法, 也可以使用下面的方法, 只是参数类型不同, 其含义一致:
let obj = QQApiAudioObject(url: URL!, title: String!, description: String!, previewImageData: Data!, targetContentType: QQApiURLTargetType)
分享视频
视频可以分享到QQ和QZone
func shareVideoToQQ() {
let url = URL(string: "视频URL地址")
let preURL = URL(string: "视频预览图片URL地址")
let obj = QQApiVideoObject(url: url!, title: "分享的视频名称", description: "视频内容描述", previewImageURL: preURL!, targetContentType: QQApiURLTargetTypeVideo)
let req = SendMessageToQQReq(content: obj)
// 分享到QQ
// QQApiInterface.send(req)
//分享到QZone
QQApiInterface.sendReq(toQZone: req)
}
这里在创建QQApiVideoObject实例的时候, 可以使用上面的方法, 也可以使用下面的方法, 只是参数类型不同, 其含义一致:
let obj = QQApiVideoObject(url: URL!, title: String!, description: String!, previewImageData: Data!, targetContentType: QQApiURLTargetType
(完)
以上便是 QQ登录分享的所有内容, 如有不正确的地方, 还请评论指出, 或者私信;
文章涉及的demo在Github LQThirdParty, 欢迎Star | Fork