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

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

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

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

然后搜索search paths,添加微信SDK文件路徑, 如果是在根目錄下, 則不需要修改, 這里是根目錄下:

設(shè)置路徑
添加URL Scheme

來到Info-> URL Types, 點(diǎn)擊左下角的 + 新加一個(gè)Scheme

scheme添加你的微信AppID即可

添加URL Scheme
適配iOS 9+ , 添加Scheme白名單
  • 方式一

在Info.plist文件內(nèi)新加字段: LSApplicationQueriesSchemes, 類型為Array(數(shù)組)
然后添加內(nèi)容, 類型為String(字符串)
微信需要添加以下字段:

wechat
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
回退到HTTP

或者以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)?

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: 這種方式需要對(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è)步驟:

  1. 獲取 code
  1. 根據(jù)code, 獲取accessToken
  2. 根據(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

(完)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末赚抡,一起剝皮案震驚了整個(gè)濱河市爬坑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌涂臣,老刑警劉巖盾计,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件售担,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡署辉,警方通過查閱死者的電腦和手機(jī)族铆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哭尝,“玉大人哥攘,你說我怎么就攤上這事〔酿校” “怎么了逝淹?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長桶唐。 經(jīng)常有香客問我栅葡,道長,這世上最難降的妖魔是什么尤泽? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任欣簇,我火速辦了婚禮,結(jié)果婚禮上坯约,老公的妹妹穿的比我還像新娘醉蚁。我一直安慰自己,他們只是感情好鬼店,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著黔龟,像睡著了一般妇智。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上氏身,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天巍棱,我揣著相機(jī)與錄音,去河邊找鬼蛋欣。 笑死航徙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的陷虎。 我是一名探鬼主播到踏,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼尚猿!你這毒婦竟也來了窝稿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤凿掂,失蹤者是張志新(化名)和其女友劉穎伴榔,沒想到半個(gè)月后纹蝴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡踪少,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年塘安,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片援奢。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡兼犯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出萝究,到底是詐尸還是另有隱情免都,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布帆竹,位于F島的核電站绕娘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏栽连。R本人自食惡果不足惜险领,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秒紧。 院中可真熱鬧绢陌,春花似錦、人聲如沸熔恢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叙淌。三九已至秤掌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鹰霍,已是汗流浹背闻鉴。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留茂洒,地道東北人孟岛。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像督勺,于是被迫代替她去往敵國和親渠羞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容