美文网首页Swift开发实战Swift 入门到入门
[Swift]原生第三方接入: 微信篇--集成/登录/分享/支付

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

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

    文章涉及的demo在Github LQThirdParty, 欢迎Star | Fork

    关于第三方登录/分享的接入, 很多时候使用的是友盟或者ShareSDK; 但并不是每次都想使用这些第三方的服务的, 这里作者整理了微信, QQ, 新浪微博原生第三方的接入:

    [Swift]原生第三方接入: 微信篇--集成/登录/分享/支付
    [Swift]原生第三方接入: QQ篇--集成/登录/分享
    [Swift]原生第三方接入: 新浪微博篇--集成/登录/分享

    一. 集成

    1.1 新建应用

    首先, 在微信-开放平台注册成为微信开发者, 然后新建APP, 获取相应的 AppIDAppSecret

    1.2 集成SDK

    微信官方集成文档

    如果使用CocoaPods集成, 直接在Podfile文件添加:
    pod 'WechatOpenSDK'

    下载微信SDK: iOS开发工具包
    解压后, 将文件内的以下三个文件拖入工程目录:

    libWeChatSDK.a
    WXApi.h
    WXApiObject.h

    添加系统依赖库

    到Build Phases -> Link Binary With Libraries

    SystemConfiguration.framework
    Security.framework
    CFNetwork.framework
    CoreTelephony.framework
    libz.dylib
    libsqlite3.0.dylib
    libc++.dylib
    后面三个新版Xcode为:
    libz.tbd
    libsqlite3.0.tbd
    libc++.tbd

    添加 -Objc -all_load

    来到Build Settings, 搜索other link 添加 -Objc -all_load

    添加 -Objc -all_load
    添加微信SDK文件路径

    然后搜索search paths,添加微信SDK文件路径, 如果是在根目录下, 则不需要修改, 这里是根目录下:

    设置路径
    添加URL Scheme

    来到Info-> URL Types, 点击左下角的 + 新加一个Scheme

    scheme添加你的微信AppID即可

    添加URL Scheme
    适配iOS 9+ , 添加Scheme白名单
    • 方式一

    在Info.plist文件内新加字段: LSApplicationQueriesSchemes, 类型为Array(数组)
    然后添加内容, 类型为String(字符串)
    微信需要添加以下字段:

    wechat
    weixin

    • 方式二

    或者, 在Info.plist文件右键, Open as... -> Source Code, 可打开文件, 进行编辑, 在倒数第三行(即: </dict> 标签的上面)的空白处添加以下代码:

    <key>LSApplicationQueriesSchemes</key>
    <array>
            <string>wechat</string>
            <string>weixin</string>
    </array>
    

    如果还有其他平台的白名单需要添加, 例如QQ, 新浪微博, 只需要在<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域

    或者以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 "WXApi.h"
    

    二. 登录

    微信的授权登录相比较于新浪微博和QQ授权复杂一些, 他需要我们调用相关的接口来获取相应的权限, 基本需要下面三个步骤:

    1. 获取 code
    1. 根据code, 获取accessToken
    2. 根据accessToken获取用户信息

    在AppDelegate.swift中注册app:

    WXApi.registerApp(wechatAppID)
    

    在方法 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.xin" {
                // 微信 的回调
                return  WXApi.handleOpen(url, delegate: self)
            }
            
            return true
    }
    

    然后, 实现其代理方法:

    func onReq(_ req: BaseReq!) {
            
        }
        
        func onResp(_ resp: BaseResp!) {
            // 这里是使用异步的方式来获取的
            let sendRes: SendAuthResp? = resp as? SendAuthResp
            let queue = DispatchQueue(label: "wechatLoginQueue")
            queue.async {
                
                print("async: \(Thread.current)")
                if let sd = sendRes {
                    if sd.errCode == 0 {
                        
                        guard (sd.code) != nil else {
                            return
                        }
                        // 第一步: 获取到code, 根据code去请求accessToken
                        self.requestAccessToken((sd.code)!)
                    } else {
                        
                        DispatchQueue.main.async {
                            // 授权失败
                        }
                    }
                } else {
                    
                    DispatchQueue.main.async {
                       // 异常
                    }
                }
            }
        }
    

    根据第一步中获取到code来请求accessToken:

    private func requestAccessToken(_ code: String) {
            // 第二步: 请求accessToken
            let urlStr = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=\(self.wechatAppID)&secret=\(self.wechatAppSecret)&code=\(code)&grant_type=authorization_code"
            
            let url = URL(string: urlStr)
            
            do {
                //                    let responseStr = try String.init(contentsOf: url!, encoding: String.Encoding.utf8)
                
                let responseData = try Data.init(contentsOf: url!, options: Data.ReadingOptions.alwaysMapped)
                
                let dic = try JSONSerialization.jsonObject(with: responseData, options: JSONSerialization.ReadingOptions.allowFragments) as? Dictionary<String, Any>
                
                guard dic != nil else {
                    DispatchQueue.main.async {
                        // 获取授权信息异常
                    }
                    return
                }
                
                guard dic!["access_token"] != nil else {
                    DispatchQueue.main.async {
                       获取授权信息异常
                    }
                    return
                }
                
                guard dic!["openid"] != nil else {
                    DispatchQueue.main.async {
                        // 获取授权信息异常
                    }
                    return
                }
                // 根据获取到的accessToken来请求用户信息
                self.requestUserInfo(dic!["access_token"]! as! String, openID: dic!["openid"]! as! String)
            } catch {
                DispatchQueue.main.async {
                    // 获取授权信息异常
                }
            }
        }
    

    根据获取到的accessToken来请求用户信息:

    private func requestUserInfo(_ accessToken: String, openID: String) {
            
            let urlStr = "https://api.weixin.qq.com/sns/userinfo?access_token=\(accessToken)&openid=\(openID)"
            
            let url = URL(string: urlStr)
            
            do {
                //                    let responseStr = try String.init(contentsOf: url!, encoding: String.Encoding.utf8)
                
                let responseData = try Data.init(contentsOf: url!, options: Data.ReadingOptions.alwaysMapped)
                
                let dic = try JSONSerialization.jsonObject(with: responseData, options: JSONSerialization.ReadingOptions.allowFragments) as? Dictionary<String, Any>
                
                guard dic != nil else {
                    DispatchQueue.main.async {
                        // 获取授权信息异常
                    }
                    
                    return
                }
                
                if let dic = dic {
                    
                    // 这个字典(dic)内包含了我们所请求回的相关用户信息
                }
            } catch {
                DispatchQueue.main.async {
                   // 获取授权信息异常
                }
            }
        }
    

    最后在需要发起登录的地方添加以下代码即可:

    let req = SendAuthReq()
            req.scope = "snsapi_userinfo"
            req.state = "default_state"
            
            WXApi.send(req)
    

    关于取消授权登录

    微信授权成功后, 第三方的APP是无法主动取消授权的, 所谓的取消授权, 在第三方APP中表现即是重新吊起微信客户端进行授权登录, 知道了这些, 我们就可以自己做出一些取消授权的"假象". 是否吊起微信客户端, 在使用微信授权登录的时候, 重要的一个参数是scope, 即发起授权请求的 SendAuthReq 类的一个参数, 如果要想吊起微信客户端, 只需要将此值设置为 snsapi_userinfo, 如果在登录的有效期内, 不想每次都吊起微信客户端, 可不设置此参数, 或者置为空"", 则就不会吊起微信客户端, 或者设置一个参数来控制是否吊起微信客户端.

    if isAlreadyAuthed {
                // 登录成功回调
            } else {
                let req = SendAuthReq()
                req.scope = "snsapi_userinfo"
                req.state = "default_state"
                
                WXApi.send(req)
            }
    

    三. 分享

    微信的分享API相对比较简单, 其官方的实例代码很清楚, 按照实例设置要分享的内容即可.
    这里我使用Swift语言编写的如下:
    以下代码中用到的 LDShareType 类型为我定义的一个枚举, 用来指定分享到哪里 :

    enum LDShareType {
        case Session, Timeline, Favorite/*会话, 朋友圈, 收藏*/
    }
    
    分享文本
     func shareText(_ text: String, to scene: LDShareType) {
            
            let req = SendMessageToWXReq()
            req.text = text
            req.bText = true
            
            switch scene {
            case .Session:
                req.scene = Int32(WXSceneSession.rawValue)
            case .Timeline:
                req.scene = Int32(WXSceneTimeline.rawValue)
            case .Favorite:
                req.scene = Int32(WXSceneFavorite.rawValue)
            }
            
            WXApi.send(req)
        }
    
    分享图片
    func shareImage(_ data: Data, thumbImage: UIImage, title: String, description: String, to scene: LDShareType) {
            
            let message = WXMediaMessage()
            message.setThumbImage(thumbImage)
            message.title = title
            message.description = description
            
            let obj = WXImageObject()
            obj.imageData = data
            message.mediaObject = obj
            
            let req = SendMessageToWXReq()
            req.bText = false
            req.message = message
            
            switch scene {
            case .Session:
                req.scene = Int32(WXSceneSession.rawValue)
            case .Timeline:
                req.scene = Int32(WXSceneTimeline.rawValue)
            case .Favorite:
                req.scene = Int32(WXSceneFavorite.rawValue)
            }
            
            WXApi.send(req)
        }
    
    分享音乐

    这里我没有设置相应的数据, 只是示例代码, 根据自己的需求配置数据即可 :

    func shareMusic(to scene: LDShareType) {
            let message = WXMediaMessage()
            message.title = "音乐标题"
            message.description = "音乐描述"
            message.setThumbImage(UIImage())
            
            let obj = WXMusicObject()
            obj.musicUrl = "音乐链接"
            obj.musicLowBandUrl = obj.musicUrl
            
            obj.musicDataUrl = "音乐数据链接地址"
            obj.musicLowBandDataUrl = obj.musicDataUrl
            message.mediaObject = obj
            
            let req = SendMessageToWXReq()
            req.bText = false
            req.message = message
            switch scene {
            case .Session:
                req.scene = Int32(WXSceneSession.rawValue)
            case .Timeline:
                req.scene = Int32(WXSceneTimeline.rawValue)
            case .Favorite:
                req.scene = Int32(WXSceneFavorite.rawValue)
            }
            
            WXApi.send(req)
        }
    
    分享视频
    func shareVideo(to scene: LDShareType) {
            
            let message = WXMediaMessage()
            message.title = "视频标题"
            message.description = "视频描述"
            message.setThumbImage(UIImage())
            
            let obj = WXVideoObject()
            obj.videoUrl = "视频链接地址"
            obj.videoLowBandUrl = "低分辨率视频地址"
            
            let req = SendMessageToWXReq()
            req.bText = false
            req.message = message
            
            switch scene {
            case .Session:
                req.scene = Int32(WXSceneSession.rawValue)
            case .Timeline:
                req.scene = Int32(WXSceneTimeline.rawValue)
            case .Favorite:
                req.scene = Int32(WXSceneFavorite.rawValue)
            }
            
            WXApi.send(req)
        }
    
    分享URL
    func shareURL(to scene: LDShareType) {
            
            let message = WXMediaMessage()
            message.title = "title"
            message.description = "description"
            message.setThumbImage(UIImage())
            
            let obj = WXWebpageObject()
            obj.webpageUrl = "http://www.baidu.com"
            message.mediaObject = obj
            
            let req = SendMessageToWXReq()
            req.bText = false
            req.message = message
            
            switch scene {
            case .Session:
                req.scene = Int32(WXSceneSession.rawValue)
            case .Timeline:
                req.scene = Int32(WXSceneTimeline.rawValue)
            case .Favorite:
                req.scene = Int32(WXSceneFavorite.rawValue)
            }
            
            WXApi.send(req)
        }
    

    以上分享的结果回调同样是在 ** func onResp(_ resp: BaseResp!) ** 代理方法内获得, 通过判断resp的类型来区分是登录还是分享:

    func onResp(_ resp: BaseResp!) {
            if resp is SendAuthResp {
                // 微信登录
               
            } else if resp is SendMessageToWXResp {
                let send = resp as? SendMessageToWXResp
                if let sm = send {
                    if sm.errCode == 0 {
                        print("分享成功")
                    } else {
                        print("分享失败")
                    }
                }
            }
            
        }
    

    四. 支付

    微信支付详细接入流程, 请参考本人的另一篇文章: [iOS]微信支付接入详解, 大致过程和本文的 第一部分. 集成很想相似. 这里不再做过的介绍,只给出Swift的写法 :
    发起微信支付

    class func pay(to identifier: String, _ dic: [String: String], resultHandle: LDWechatShare_payResultHandle? = nil) {
            
            LDWechatShare.shared.payResultHandle = resultHandle
            LDWechatShare.shared.payIdentifier = identifier
            
            let req = PayReq()
            
            req.partnerId = dic["partnerid"]!
            req.prepayId = dic["prepayid"]!
            req.package = dic["package"]!
            req.nonceStr = dic["noncestr"]!
            req.timeStamp = UInt32(dic["timestamp"]!)!
            req.sign = dic["sign"]
            
            WXApi.send(req)
        }
    

    这里吊起微信支付的参数(即PayReq的参数), 中需要注意的是sign, 这里没有做任何签名, 所有的都是后台完成的, 包括该值的二次签名, 一定要二次签名, 不然掉不起微信支付;

    获取支付结果:

    func onResp(_ resp: BaseResp!) {
            if resp is SendAuthResp {
                // 微信登录
            } else if resp is SendMessageToWXResp {
                // 分享
            } else if resp is PayResp { 
                // 支付
                var rs: LDWechatPayResult = .Failed
                
                switch resp.errCode {
                case WXSuccess.rawValue:
                    rs = .Success
                case WXErrCodeUserCancel.rawValue:
                    rs = .Cancel
                default:
                    rs = .Failed
                }
                
                if let handle = self.payResultHandle {
                    handle(rs, self.payIdentifier)
                }
            }
        }
    

    这里我是已闭包的形式将支付结果回调的, 可能我设置一个标示符参数** identifier** 会使用不到, 这个是用来标识是哪个控制器发起的微信支付请求, 因为一个工程里, 可能发起支付的控制器会不止一个, 当有两个以上的控制器被创建时, 就有可能是多个控制器收到支付结果. 如果是使用通知来发送支付结果, 为避免多个控制器收到支付结果, 可能就会使用到这个标示符.

    以上便是微信登录分享的所有内容, 如有不正确的地方, 还请评论指出, 或者私信;

    文章涉及的demo在Github LQThirdParty, 欢迎Star | Fork

    (完)

    相关文章

      网友评论

      • 391c76ad2e0a:写的不错,先赞一个,有个问题请教一下,在分享音乐到微信联系人时,每次都发送失败(前面一个红点感叹号),其他类型的分享则都没问题。不知道遇到过这个问题没?怎么解决?

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

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