美文网首页Swift开发实战iOS开发基础Swift 入门到入门
[Swift]原生第三方接入: QQ篇--集成/登录/分享

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

作者: 流火绯瞳 | 来源:发表于2017-06-12 14:44 被阅读2003次

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

    相关文章

      网友评论

      • 姑娘丶你命里缺我:楼主,微信分享和QQ分享SDK回调方法冲突是怎么解决的呢?
        流火绯瞳:@姑娘丶你命里缺我 使用两个不同的对象作为代理,也就是在不同的类中进行封装
      • Alan龙马:你好,我下载sdk 下来没看到 TencentOpenApi_IOS_Bundle.bundle 这个文件,知道怎么回事吗
      • 千年积木:谢谢分享
      • 38d27e64c747:谢谢分享 找个时间好好 研究一下

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

      本文链接:https://www.haomeiwen.com/subject/itroxxtx.html