[Swift]原生第三方接入: 微信篇--集成/登录/分享/支付
文章涉及的demo在Github LQThirdParty, 欢迎Star | Fork
关于第三方登录/分享的接入, 很多时候使用的是友盟或者ShareSDK; 但并不是每次都想使用这些第三方的服务的, 这里作者整理了微信, QQ, 新浪微博原生第三方的接入:
[Swift]原生第三方接入: 微信篇--集成/登录/分享/支付
[Swift]原生第三方接入: QQ篇--集成/登录/分享
[Swift]原生第三方接入: 新浪微博篇--集成/登录/分享
一. 集成
1.1 新建应用
首先, 在微信-开放平台注册成为微信开发者, 然后新建APP, 获取相应的 AppID 和 AppSecret
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
添加URL Schemescheme添加你的微信AppID即可
适配iOS 9+ , 添加Scheme白名单
- 方式一
在Info.plist文件内新加字段: LSApplicationQueriesSchemes, 类型为Array(数组)
然后添加内容, 类型为String(字符串)
微信需要添加以下字段:
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
或者以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授权复杂一些, 他需要我们调用相关的接口来获取相应的权限, 基本需要下面三个步骤:
- 获取 code
- 根据code, 获取accessToken
- 根据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