[Swift]原生第三方接入: QQ篇--集成/登录/分享

文章涉及的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

添加URL Scheme
适配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, 可打开文件, 进行编辑, 在倒数第三行(即: </dict> 标签的上面)的空白处添加以下代码:

<key>LSApplicationQueriesSchemes</key>
<array>
        <string>mqzoneopensdk</string>
        <string>mqzoneopensdkapi</string>
        <string>mqzoneopensdkapi19</string>
        <string>mqzoneopensdkapiV2</string>
        <string>mqqOpensdkSSoLogin</string>
        <string>mqqopensdkapiV2</string>
        <string>mqqopensdkapiV3</string>
        <string>wtloginmqq2</string>
        <string>mqqapi</string>
        <string>mqqwpa</string>
        <string>mqzone</string>
        <string>mqq</string>
        <string>mqqopensdkapiV4</string>
                <string> mqqopensdkdataline </string>
</array>

如果还有其他平台的白名单需要添加, 例如微信, 新浪微博, 只需要在<array></array>标签内添加对应的字段即可;

PS: Info.plist文件显示为Source Code后, 如果还想显示原来的列表格式, 可以: 邮件 -> Open as.. -> Property List 即可.

适配iOS 9+, 网络请求
  • **方式一: 暂时回退到HTTP请求 **
    在Info.plist文件中添加字段: ** NSAppTransportSecurity , 类型为字典;
    然后添加一个Key:
    NSAllowsArbitraryLoads **, 类型为Boolean, 值为 YES
回退到HTTP

或者以Source Code 打开Info.plist文件, 空白处添加以下代码:

<key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>
  • ** 方式二: 设置域 **

在项目的info.plist中添加一个Key:NSAppTransportSecurity,类型为字典类型。
然后给它添加一个值: NSExceptionDomains,类型为字典类型;
把需要的支持的域添加給NSExceptionDomains
其中域作为Key,类型为字典类型。
每个域下面需要设置3个属性:
NSIncludesSubdomains、
NSExceptionRequiresForwardSecrecy、NSExceptionAllowsInsecureHTTPLoads。

均为Boolean类型,值分别为YES、NO、YES

QQ需要设置的域为:

QQ域

或者以Source Code 打开Info.plist文件, 空白处添加以下代码:

<key>NSAppTransportSecurity</key>
    <dict>
        <key>NSExceptionDomains</key>
        <dict>
            <key>qq.com</key>
            <dict>
                <key>NSExceptionAllowsInsecureHTTPLoads</key>
                <true/>
                <key>NSExceptionRequiresForwardSecrecy</key>
                <false/>
                <key>NSIncludesSubdomains</key>
                <true/>
            </dict>
        </dict>
    </dict>

PS: 这种方式需要对每个要以HTTP方式访问的域名进行设置, 比较麻烦, 建议使用第一种方式.

到此, 集成及适配结束...

PS: 在使用相关API的时候, 需要新建桥接头文件, 或者在已有桥接头文件内引用其头文件:

#import <TencentOpenAPI/TencentOAuth.h>
#import <TencentOpenAPI/QQApiInterfaceObject.h>
#import <TencentOpenAPI/QQApiInterface.h>

二. 登录

在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版本都比较高了, 至于低于哪个版本需要兼容, 官方文档没有提, 如果你的开发中适配低版本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

推荐阅读更多精彩内容