1.獲取未授權(quán)Request
// 1.定義字符串保存登錄界面URL
let urlStr = "https://api.weibo.com/oauth2/authorize?client_id=4129759360&redirect_uri=http://www.520it.com"
// 2.創(chuàng)建URL
guard let url = URL(string: urlStr) else
{
return
}
// 3.創(chuàng)建Request
let request = URLRequest(url: url)
// 4.加載登錄界面
customWebView.loadRequest(request)
2.獲取已經(jīng)授權(quán)Request
extension OAuthViewController: UIWebViewDelegate
{
// 該方法每次請求都會調(diào)用
// 如果返回false代表不允許請求, 如果返回true代表允許請求
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
/*
登錄界面: https://api.weibo.com/oauth2/authorize?client_id=4129759360&redirect_uri=http://www.520it.com
輸入賬號密碼之后: https://api.weibo.com/oauth2/authorize
取消授權(quán): http://www.520it.com/?error_uri=%2Foauth2%2Fauthorize&error=access_denied&error_description=user%20denied%20your%20request.&error_code=21330
授權(quán):http://www.520it.com/?code=c2796542e264da89367f993131e6c904
通過觀察
1.如果是授權(quán)成功獲取失敗都會跳轉(zhuǎn)到授權(quán)回調(diào)頁面
2.如果授權(quán)回調(diào)頁面包含code=就代表授權(quán)成功, 需要截取code=后面字符串
3.而且如果是授權(quán)回調(diào)頁面不需要顯示給用戶看, 返回false
*/
// 1.判斷當前是否是授權(quán)回調(diào)頁面
guard let urlStr = request.url?.absoluteString else
{
return false
}
if !urlStr.hasPrefix("http://www.520it.com/")
{
NJLog("不是授權(quán)回調(diào)頁面")
return true
}
NJLog("是授權(quán)回調(diào)頁面")
// 2.判斷授權(quán)回調(diào)地址中是否包含code=
// URL的query屬性是專門用于獲取URL中的參數(shù)的, 可以獲取URL中?后面的所有內(nèi)容
let key = "code="
if urlStr.contains(key)
{
let code = request.url!.query?.substring(from: key.endIndex)
NJLog(code)
return false
}
NJLog("授權(quán)失敗")
return false
}
}
3.換取AccessToken
/// 利用RequestToken換取AccessToken
private func loadAccessToken(codeStr: String?)
{
guard let code = codeStr else
{
return
}
// 注意:redirect_uri必須和開發(fā)中平臺中填寫的一模一樣
// 1.準備請求路徑
let path = "oauth2/access_token"
// 2.準備請求參數(shù)
let parameters = ["client_id": "4129759360", "client_secret": "98392a5714c6194f5aee796d971fe0ef", "grant_type": "authorization_code", "code": code, "redirect_uri": "http://www.520it.com"]
// 3.發(fā)送POST請求
NetworkTools.shareInstance.POST(path, parameters: parameters, success: { (task: NSURLSessionDataTask, dict: AnyObject) -> Void in
NJLog(dict)
}) { (task: NSURLSessionDataTask?, error: NSError) -> Void in
NJLog(error)
}
}
4.保存
let account = UserAccount(dict: objc as! [String : AnyObject])
account.saveAccount()
UserAccount.swift
import UIKit
class UserAccount: NSObject, NSCoding {
var access_token: String?
var expires_in: Int = 0
var uid: String?
// MARK: - 生命周期方法
init(dict: [String: AnyObject])
{
super.init()
// 如果要想初始化方法中使用KVC必須先調(diào)用super.init初始化對象
// 如果屬性是基本數(shù)據(jù)類型, 那么建議不要使用可選類型, 因為基本數(shù)據(jù)類型的可選類型在super.init()方法中不會分配存儲空間
self.setValuesForKeysWithDictionary(dict)
}
// 當KVC發(fā)現(xiàn)沒有對應的key時就會調(diào)用
override func setValue(value: AnyObject?, forUndefinedKey key: String) {
}
override var description: String {
return "abc"
}
// MARK: - 外部控制方法
// 歸檔模型
func saveAccount() -> Bool
{
// 1.獲取緩存目錄的路徑
let path = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.CachesDirectory, NSSearchPathDomainMask.UserDomainMask, true).last!
// 2.生成緩存路徑
let filePath = (path as NSString).stringByAppendingPathComponent("useraccount.plist")
NJLog(filePath)
// 3.歸檔對象
return NSKeyedArchiver.archiveRootObject(self, toFile: filePath)
}
/// 定義屬性保存授權(quán)模型
static var account: UserAccount?
// 解歸檔模型
class func loadUserAccount() -> UserAccount?
{
// 1.判斷是否已經(jīng)加載過了
if UserAccount.account != nil{
NJLog("已經(jīng)有加載過")
// 直接返回
return UserAccount.account
}
// 2.嘗試從文件中加載
NJLog("還沒有加載過")
// 1.獲取緩存目錄的路徑
let path = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.CachesDirectory, NSSearchPathDomainMask.UserDomainMask, true).last!
// 2.生成緩存路徑
let filePath = (path as NSString).stringByAppendingPathComponent("useraccount.plist")
// 3.解歸檔對象
guard let account = NSKeyedUnarchiver.unarchiveObjectWithFile(filePath) as? UserAccount else
{
return UserAccount.account
}
UserAccount.account = account
return UserAccount.account
}
/// 判斷用戶是否登錄
class func isLogin() -> Bool {
return UserAccount.loadUserAccount() != nil
}
// MARK: - NSCoding
func encodeWithCoder(aCoder: NSCoder)
{
aCoder.encodeObject(access_token, forKey: "access_token")
aCoder.encodeInteger(expires_in, forKey: "expires_in")
aCoder.encodeObject(uid, forKey: "uid")
}
required init?(coder aDecoder: NSCoder)
{
self.access_token = aDecoder.decodeObjectForKey("access_token") as? String
self.expires_in = aDecoder.decodeIntegerForKey("expires_in") as Int
self.uid = aDecoder.decodeObjectForKey("uid") as? String
}
}
5.保存優(yōu)化
import UIKit
class UserAccount: NSObject, NSCoding {
var access_token: String?
var expires_in: Int = 0
var uid: String?
// MARK: - 生命周期方法
init(dict: [String: AnyObject])
{
super.init()
// 如果要想初始化方法中使用KVC必須先調(diào)用super.init初始化對象
// 如果屬性是基本數(shù)據(jù)類型, 那么建議不要使用可選類型, 因為基本數(shù)據(jù)類型的可選類型在super.init()方法中不會分配存儲空間
self.setValuesForKeysWithDictionary(dict)
}
// 當KVC發(fā)現(xiàn)沒有對應的key時就會調(diào)用
override func setValue(value: AnyObject?, forUndefinedKey key: String) {
}
override var description: String {
// 將模型轉(zhuǎn)換為字典
let property = ["access_token", "expires_in", "uid"]
let dict = dictionaryWithValuesForKeys(property)
// 將字典轉(zhuǎn)換為字符串
return "\(dict)"
}
// MARK: - 外部控制方法
// 歸檔模型
func saveAccount() -> Bool
{
/*
// 1.獲取緩存目錄的路徑
let path = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.CachesDirectory, NSSearchPathDomainMask.UserDomainMask, true).last!
// 2.生成緩存路徑
let filePath = (path as NSString).stringByAppendingPathComponent("useraccount.plist")
NJLog(filePath)
*/
// 3.歸檔對象
return NSKeyedArchiver.archiveRootObject(self, toFile: UserAccount.filePath)
}
/// 定義屬性保存授權(quán)模型
static var account: UserAccount?
// static let filePath: String = (NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.CachesDirectory, NSSearchPathDomainMask.UserDomainMask, true).last! as NSString).stringByAppendingPathComponent("useraccount.plist")
static let filePath: String = "useraccount.plist".cachesDir()
// 解歸檔模型
class func loadUserAccount() -> UserAccount?
{
// 1.判斷是否已經(jīng)加載過了
if UserAccount.account != nil{
NJLog("已經(jīng)有加載過")
// 直接返回
return UserAccount.account
}
// 2.嘗試從文件中加載
NJLog("還沒有加載過")
/*
// 1.獲取緩存目錄的路徑
let path = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.CachesDirectory, NSSearchPathDomainMask.UserDomainMask, true).last!
// 2.生成緩存路徑
let filePath = (path as NSString).stringByAppendingPathComponent("useraccount.plist")
*/
// 3.解歸檔對象
guard let account = NSKeyedUnarchiver.unarchiveObjectWithFile(UserAccount.filePath) as? UserAccount else
{
return UserAccount.account
}
UserAccount.account = account
return UserAccount.account
}
/// 判斷用戶是否登錄
class func isLogin() -> Bool {
return UserAccount.loadUserAccount() != nil
}
// MARK: - NSCoding
func encodeWithCoder(aCoder: NSCoder)
{
aCoder.encodeObject(access_token, forKey: "access_token")
aCoder.encodeInteger(expires_in, forKey: "expires_in")
aCoder.encodeObject(uid, forKey: "uid")
}
required init?(coder aDecoder: NSCoder)
{
self.access_token = aDecoder.decodeObjectForKey("access_token") as? String
self.expires_in = aDecoder.decodeIntegerForKey("expires_in") as Int
self.uid = aDecoder.decodeObjectForKey("uid") as? String
}
}
6.時間處理
/// 從授權(quán)那一刻開始, 多少秒之后過期時間
var expires_in: Int = 0
{
didSet{
// 生成正在過期時間
expires_Date = NSDate(timeIntervalSinceNow: NSTimeInterval(expires_in))
}
}
/// 真正過期時間
var expires_Date: NSDate?
guard let date = account.expires_Date where date.compare(NSDate()) != NSComparisonResult.OrderedAscending else
{
NJLog("過期了")
return nil
}
7.獲取用戶數(shù)據(jù)
/// 獲取用戶信息
func loadUserInfo(finished: (account: UserAccount?, error: NSError?)->())
{
// 斷言
// 斷定access_token一定是不等于nil的, 如果運行的時access_token等于nil, 那么程序就會崩潰, 并且報錯
assert(access_token != nil, "使用該方法必須先授權(quán)")
// 1.準備請求路徑
let path = "2/users/show.json"
// 2.準備請求參數(shù)
let parameters = ["access_token": access_token!, "uid": uid!]
// 3.發(fā)送GET請求
NetworkTools.shareInstance.GET(path, parameters: parameters, success: { (task, objc) -> Void in
let dict = objc as! [String: AnyObject]
// 1.取出用戶信息
self.avatar_large = dict["avatar_large"] as? String
self.screen_name = dict["screen_name"] as? String
// 2.保存授權(quán)信息
// self.saveAccount()
finished(account: self, error: nil)
}) { (task, error) -> Void in
finished(account: nil, error: error)
}
}
8.新版本特性
import UIKit
import SnapKit
class NewfeatureViewController: UIViewController {
/// 新特性界面的個數(shù)
private var maxCount = 4
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
extension NewfeatureViewController: UICollectionViewDataSource
{
// 1.告訴系統(tǒng)有多少組
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
// 2.告訴系統(tǒng)每組多少行
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return maxCount
}
// 3.告訴系統(tǒng)每行顯示什么內(nèi)容
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// 1.獲取cell
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("newfeatureCell", forIndexPath: indexPath) as! XMGNewfeatureCell
cell.backgroundColor = (indexPath.item % 2 == 0) ? UIColor.redColor() : UIColor.purpleColor()
// 2.設置數(shù)據(jù)
cell.index = indexPath.item
// 3.返回cell
return cell
}
}
extension NewfeatureViewController: UICollectionViewDelegate
{
func collectionView(collectionView: UICollectionView, didEndDisplayingCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath) {
// 注意: 傳入的cell和indexPath都是上一頁的, 而不是當前頁
// NJLog(indexPath.item)
// 1.手動獲取當前顯示的cell對應的indexPath
let index = collectionView.indexPathsForVisibleItems().last!
NJLog(index.item)
// 2.根據(jù)指定的indexPath獲取當前顯示的cell
let currentCell = collectionView.cellForItemAtIndexPath(index) as! XMGNewfeatureCell
// 3.判斷當前是否是最后一頁
if index.item == (maxCount - 1)
{
// 做動畫
currentCell.startAniamtion()
}
}
}
// MARK: - 自定義Cell
class XMGNewfeatureCell: UICollectionViewCell
{
var index: Int = 0
{
didSet{
// 1.生成圖片名稱
let name = "new_feature_\(index + 1)"
// 2.設置圖片
iconView.image = UIImage(named: name)
startButton.hidden = true
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// 初始化UI
setupUI()
}
// MARK: - 外部控制方法
func startAniamtion()
{
startButton.hidden = false
// 執(zhí)行放大動畫
/*
第一個參數(shù): 動畫時間
第二個參數(shù): 延遲時間
第三個參數(shù): 震幅 0.0~1.0, 值越小震動越列害
第四個參數(shù): 加速度, 值越大震動越列害
第五個參數(shù): 動畫附加屬性
第六個參數(shù): 執(zhí)行動畫的block
第七個參數(shù): 執(zhí)行完畢后回調(diào)的block
*/
startButton.transform = CGAffineTransformMakeScale(0.0, 0.0)
startButton.userInteractionEnabled = false
UIView.animateWithDuration(2.0, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 10, options: UIViewAnimationOptions(rawValue: 0), animations: { () -> Void in
self.startButton.transform = CGAffineTransformIdentity
}, completion: { (_) -> Void in
self.startButton.userInteractionEnabled = true
})
}
// MARK: - 內(nèi)部控制方法
private func setupUI()
{
// 1.添加子控件
contentView.addSubview(iconView)
contentView.addSubview(startButton)
// 2.布局子控件
/*
iconView.translatesAutoresizingMaskIntoConstraints = false
var cons = NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[iconView]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["iconView": iconView])
cons += NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[iconView]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["iconView": iconView])
contentView.addConstraints(cons)
*/
iconView.snp_makeConstraints { (make) -> Void in
// make.left.equalTo(0)
// make.right.equalTo(0)
// make.top.equalTo(0)
// make.bottom.equalTo(0)
make.edges.equalTo(0)
}
startButton.snp_makeConstraints { (make) -> Void in
make.centerX.equalTo(contentView)
make.bottom.equalTo(contentView).offset(-160)
}
}
@objc private func startBtnClick()
{
NJLog("")
}
// MARK: - 懶加載
/// 圖片容器
private lazy var iconView = UIImageView()
/// 開始按鈕
private lazy var startButton: UIButton = {
let btn = UIButton(imageName: nil, backgroundImageName: "new_feature_button")
btn.addTarget(self, action: Selector("startBtnClick"), forControlEvents: UIControlEvents.TouchUpInside)
return btn
}()
}
// MARK: - 自定義布局
class XMGNewfeatureLayout: UICollectionViewFlowLayout
{
// 準備布局
override func prepareLayout() {
// 1.設置每個cell的尺寸
itemSize = UIScreen.mainScreen().bounds.size
// 2.設置cell之間的間隙
minimumInteritemSpacing = 0
minimumLineSpacing = 0
// 3.設置滾動方向
scrollDirection = UICollectionViewScrollDirection.Horizontal
// 4.設置分頁
collectionView?.pagingEnabled = true
// 5.禁用回彈
collectionView?.bounces = false
// 6.取出滾動條
collectionView?.showsHorizontalScrollIndicator = false
collectionView?.showsVerticalScrollIndicator = false
}
}
9.檢查新版本
/// 判斷是否有新版本
private func isNewVersion() -> Bool
{
// 1.加載info.plist
// 2.獲取當前軟件的版本號
let currentVersion = NSBundle.mainBundle().infoDictionary!["CFBundleShortVersionString"] as! String
// 3.獲取以前的軟件版本號?
let defaults = NSUserDefaults.standardUserDefaults()
let sanboxVersion = (defaults.objectForKey("xxoo") as? String) ?? "0.0"
// 4.用當前的版本號和以前的版本號進行比較
// 1.0 0.0
if currentVersion.compare(sanboxVersion) == NSComparisonResult.OrderedDescending
{
// 如果當前的大于以前的, 有新版本
NJLog("有新版本")
// 如果有新版本, 就利用新版本的版本號更新本地的版本號
defaults.setObject(currentVersion, forKey: "xxoo")
defaults.synchronize() // iOS7以前需要寫, iOS7以后不用寫
return true
}
NJLog("沒有新版本")
return false
}