文章涉及的demo在Github LQThirdParty, 歡迎Star | Fork
關(guān)于第三方登錄/分享的接入, 很多時(shí)候使用的是友盟或者ShareSDK; 但并不是每次都想使用這些第三方的服務(wù)的, 這里作者整理了微信, QQ, 新浪微博原生第三方的接入:
[Swift]原生第三方接入: 微信篇--集成/登錄/分享/支付
[Swift]原生第三方接入: QQ篇--集成/登錄/分享
[Swift]原生第三方接入: 新浪微博篇--集成/登錄/分享
一. 集成
1.1 新建應(yīng)用
首先, 在微信-開放平臺(tái)注冊(cè)成為微信開發(fā)者, 然后新建APP, 獲取相應(yīng)的 AppID 和 AppSecret
1.2 集成SDK
如果使用CocoaPods集成, 直接在Podfile文件添加:
pod 'WechatOpenSDK'
下載微信SDK: iOS開發(fā)工具包
解壓后, 將文件內(nèi)的以下三個(gè)文件拖入工程目錄:
libWeChatSDK.a
WXApi.h
WXApiObject.h
添加系統(tǒng)依賴庫
到Build Phases -> Link Binary With Libraries
SystemConfiguration.framework
Security.framework
CFNetwork.framework
CoreTelephony.framework
libz.dylib
libsqlite3.0.dylib
libc++.dylib
后面三個(gè)新版Xcode為:
libz.tbd
libsqlite3.0.tbd
libc++.tbd
添加 -Objc -all_load
來到Build Settings, 搜索other link 添加 -Objc -all_load
添加微信SDK文件路徑
然后搜索search paths,添加微信SDK文件路徑, 如果是在根目錄下, 則不需要修改, 這里是根目錄下:
添加URL Scheme
來到Info-> URL Types, 點(diǎn)擊左下角的 + 新加一個(gè)Scheme
scheme添加你的微信AppID即可
適配iOS 9+ , 添加Scheme白名單
- 方式一
在Info.plist文件內(nèi)新加字段: LSApplicationQueriesSchemes, 類型為Array(數(shù)組)
然后添加內(nèi)容, 類型為String(字符串)
微信需要添加以下字段:
weixin
- 方式二
或者, 在Info.plist文件右鍵, Open as... -> Source Code, 可打開文件, 進(jìn)行編輯, 在倒數(shù)第三行(即: </dict> 標(biāo)簽的上面)的空白處添加以下代碼:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>wechat</string>
<string>weixin</string>
</array>
如果還有其他平臺(tái)的白名單需要添加, 例如QQ, 新浪微博, 只需要在<array></array>標(biāo)簽內(nèi)添加對(duì)應(yīng)的字段即可;
PS: Info.plist文件顯示為Source Code后, 如果還想顯示原來的列表格式, 可以: 郵件 -> Open as.. -> Property List 即可
適配iOS 9+, 網(wǎng)絡(luò)請(qǐng)求
-
方式一: 暫時(shí)回退到HTTP請(qǐng)求
在Info.plist文件中添加字段: NSAppTransportSecurity, 類型為字典;
然后添加一個(gè)Key:NSAllowsArbitraryLoads, 類型為Boolean, 值為 YES
或者以Source Code 打開Info.plist文件, 空白處添加以下代碼:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
- 方式二: 設(shè)置域
在項(xiàng)目的info.plist中添加一個(gè)Key:NSAppTransportSecurity书幕,類型為字典類型嗜侮。
然后給它添加一個(gè)值: NSExceptionDomains璃氢,類型為字典類型刊棕;
把需要的支持的域添加給NSExceptionDomains
其中域作為Key欺缘,類型為字典類型。
每個(gè)域下面需要設(shè)置3個(gè)屬性:
NSIncludesSubdomains马篮、
NSExceptionRequiresForwardSecrecy粟判、NSExceptionAllowsInsecureHTTPLoads。
均為Boolean類型妖胀,值分別為YES芥颈、NO、YES
微信需要設(shè)置的域?yàn)?
或者以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: 這種方式需要對(duì)每個(gè)要以HTTP方式訪問的域名進(jìn)行設(shè)置, 比較麻煩, 建議使用第一種方式.
到此, 集成及適配結(jié)束...
PS: 在使用相關(guān)API的時(shí)候, 需要新建橋接頭文件, 或者在已有橋接頭文件內(nèi)引用其頭文件:
#import "WXApi.h"
二. 登錄
微信的授權(quán)登錄相比較于新浪微博和QQ授權(quán)復(fù)雜一些, 他需要我們調(diào)用相關(guān)的接口來獲取相應(yīng)的權(quán)限, 基本需要下面三個(gè)步驟:
- 獲取 code
- 根據(jù)code, 獲取accessToken
- 根據(jù)accessToken獲取用戶信息
在AppDelegate.swift中注冊(cè)app:
WXApi.registerApp(wechatAppID)
在方法 func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool 中添加回調(diào):
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
let urlKey: String = options[UIApplicationOpenURLOptionsKey.sourceApplication] as! String
if urlKey == "com.tencent.xin" {
// 微信 的回調(diào)
return WXApi.handleOpen(url, delegate: self)
}
return true
}
然后, 實(shí)現(xiàn)其代理方法:
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, 根據(jù)code去請(qǐng)求accessToken
self.requestAccessToken((sd.code)!)
} else {
DispatchQueue.main.async {
// 授權(quán)失敗
}
}
} else {
DispatchQueue.main.async {
// 異常
}
}
}
}
根據(jù)第一步中獲取到code來請(qǐng)求accessToken:
private func requestAccessToken(_ code: String) {
// 第二步: 請(qǐng)求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 {
// 獲取授權(quán)信息異常
}
return
}
guard dic!["access_token"] != nil else {
DispatchQueue.main.async {
獲取授權(quán)信息異常
}
return
}
guard dic!["openid"] != nil else {
DispatchQueue.main.async {
// 獲取授權(quán)信息異常
}
return
}
// 根據(jù)獲取到的accessToken來請(qǐng)求用戶信息
self.requestUserInfo(dic!["access_token"]! as! String, openID: dic!["openid"]! as! String)
} catch {
DispatchQueue.main.async {
// 獲取授權(quán)信息異常
}
}
}
根據(jù)獲取到的accessToken來請(qǐng)求用戶信息:
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 {
// 獲取授權(quán)信息異常
}
return
}
if let dic = dic {
// 這個(gè)字典(dic)內(nèi)包含了我們所請(qǐng)求回的相關(guān)用戶信息
}
} catch {
DispatchQueue.main.async {
// 獲取授權(quán)信息異常
}
}
}
最后在需要發(fā)起登錄的地方添加以下代碼即可:
let req = SendAuthReq()
req.scope = "snsapi_userinfo"
req.state = "default_state"
WXApi.send(req)
關(guān)于取消授權(quán)登錄
微信授權(quán)成功后, 第三方的APP是無法主動(dòng)取消授權(quán)的, 所謂的取消授權(quán), 在第三方APP中表現(xiàn)即是重新吊起微信客戶端進(jìn)行授權(quán)登錄, 知道了這些, 我們就可以自己做出一些取消授權(quán)的"假象". 是否吊起微信客戶端, 在使用微信授權(quán)登錄的時(shí)候, 重要的一個(gè)參數(shù)是scope, 即發(fā)起授權(quán)請(qǐng)求的 SendAuthReq 類的一個(gè)參數(shù), 如果要想吊起微信客戶端, 只需要將此值設(shè)置為 snsapi_userinfo, 如果在登錄的有效期內(nèi), 不想每次都吊起微信客戶端, 可不設(shè)置此參數(shù), 或者置為空"", 則就不會(huì)吊起微信客戶端, 或者設(shè)置一個(gè)參數(shù)來控制是否吊起微信客戶端.
if isAlreadyAuthed {
// 登錄成功回調(diào)
} else {
let req = SendAuthReq()
req.scope = "snsapi_userinfo"
req.state = "default_state"
WXApi.send(req)
}
三. 分享
微信的分享API相對(duì)比較簡單, 其官方的實(shí)例代碼很清楚, 按照實(shí)例設(shè)置要分享的內(nèi)容即可.
這里我使用Swift語言編寫的如下:
以下代碼中用到的 LDShareType 類型為我定義的一個(gè)枚舉, 用來指定分享到哪里 :
enum LDShareType {
case Session, Timeline, Favorite/*會(huì)話, 朋友圈, 收藏*/
}
分享文本
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)
}
分享音樂
這里我沒有設(shè)置相應(yīng)的數(shù)據(jù), 只是示例代碼, 根據(jù)自己的需求配置數(shù)據(jù)即可 :
func shareMusic(to scene: LDShareType) {
let message = WXMediaMessage()
message.title = "音樂標(biāo)題"
message.description = "音樂描述"
message.setThumbImage(UIImage())
let obj = WXMusicObject()
obj.musicUrl = "音樂鏈接"
obj.musicLowBandUrl = obj.musicUrl
obj.musicDataUrl = "音樂數(shù)據(jù)鏈接地址"
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 = "視頻標(biāo)題"
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)
}
以上分享的結(jié)果回調(diào)同樣是在 ** func onResp(_ resp: BaseResp!) ** 代理方法內(nèi)獲得, 通過判斷resp的類型來區(qū)分是登錄還是分享:
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("分享失敗")
}
}
}
}
四. 支付
微信支付詳細(xì)接入流程, 請(qǐng)參考本人的另一篇文章: [iOS]微信支付接入詳解, 大致過程和本文的 第一部分. 集成很想相似. 這里不再做過的介紹,只給出Swift的寫法 :
發(fā)起微信支付
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)
}
這里吊起微信支付的參數(shù)(即PayReq的參數(shù)), 中需要注意的是sign, 這里沒有做任何簽名, 所有的都是后臺(tái)完成的, 包括該值的二次簽名, 一定要二次簽名, 不然掉不起微信支付;
獲取支付結(jié)果:
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)
}
}
}
這里我是已閉包的形式將支付結(jié)果回調(diào)的, 可能我設(shè)置一個(gè)標(biāo)示符參數(shù)** identifier** 會(huì)使用不到, 這個(gè)是用來標(biāo)識(shí)是哪個(gè)控制器發(fā)起的微信支付請(qǐng)求, 因?yàn)橐粋€(gè)工程里, 可能發(fā)起支付的控制器會(huì)不止一個(gè), 當(dāng)有兩個(gè)以上的控制器被創(chuàng)建時(shí), 就有可能是多個(gè)控制器收到支付結(jié)果. 如果是使用通知來發(fā)送支付結(jié)果, 為避免多個(gè)控制器收到支付結(jié)果, 可能就會(huì)使用到這個(gè)標(biāo)示符.
以上便是微信登錄分享的所有內(nèi)容, 如有不正確的地方, 還請(qǐng)?jiān)u論指出, 或者私信;
文章涉及的demo在Github LQThirdParty, 歡迎Star | Fork