1.获取未授权Request
// 1.定义字符串保存登录界面URL
let urlStr = "https://api.weibo.com/oauth2/authorize?client_id=4129759360&redirect_uri=http://www.520it.com"
// 2.创建URL
guard let url = URL(string: urlStr) else
{
return
}
// 3.创建Request
let request = URLRequest(url: url)
// 4.加载登录界面
customWebView.loadRequest(request)
2.获取已经授权Request
extension OAuthViewController: UIWebViewDelegate
{
// 该方法每次请求都会调用
// 如果返回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
取消授权: http://www.520it.com/?error_uri=%2Foauth2%2Fauthorize&error=access_denied&error_description=user%20denied%20your%20request.&error_code=21330
授权:http://www.520it.com/?code=c2796542e264da89367f993131e6c904
通过观察
1.如果是授权成功获取失败都会跳转到授权回调页面
2.如果授权回调页面包含code=就代表授权成功, 需要截取code=后面字符串
3.而且如果是授权回调页面不需要显示给用户看, 返回false
*/
// 1.判断当前是否是授权回调页面
guard let urlStr = request.url?.absoluteString else
{
return false
}
if !urlStr.hasPrefix("http://www.520it.com/")
{
NJLog("不是授权回调页面")
return true
}
NJLog("是授权回调页面")
// 2.判断授权回调地址中是否包含code=
// URL的query属性是专门用于获取URL中的参数的, 可以获取URL中?后面的所有内容
let key = "code="
if urlStr.contains(key)
{
let code = request.url!.query?.substring(from: key.endIndex)
NJLog(code)
return false
}
NJLog("授权失败")
return false
}
}
3.换取AccessToken
/// 利用RequestToken换取AccessToken
private func loadAccessToken(codeStr: String?)
{
guard let code = codeStr else
{
return
}
// 注意:redirect_uri必须和开发中平台中填写的一模一样
// 1.准备请求路径
let path = "oauth2/access_token"
// 2.准备请求参数
let parameters = ["client_id": "4129759360", "client_secret": "98392a5714c6194f5aee796d971fe0ef", "grant_type": "authorization_code", "code": code, "redirect_uri": "http://www.520it.com"]
// 3.发送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必须先调用super.init初始化对象
// 如果属性是基本数据类型, 那么建议不要使用可选类型, 因为基本数据类型的可选类型在super.init()方法中不会分配存储空间
self.setValuesForKeysWithDictionary(dict)
}
// 当KVC发现没有对应的key时就会调用
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)
}
/// 定义属性保存授权模型
static var account: UserAccount?
// 解归档模型
class func loadUserAccount() -> UserAccount?
{
// 1.判断是否已经加载过了
if UserAccount.account != nil{
NJLog("已经有加载过")
// 直接返回
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.保存优化
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必须先调用super.init初始化对象
// 如果属性是基本数据类型, 那么建议不要使用可选类型, 因为基本数据类型的可选类型在super.init()方法中不会分配存储空间
self.setValuesForKeysWithDictionary(dict)
}
// 当KVC发现没有对应的key时就会调用
override func setValue(value: AnyObject?, forUndefinedKey key: String) {
}
override var description: String {
// 将模型转换为字典
let property = ["access_token", "expires_in", "uid"]
let dict = dictionaryWithValuesForKeys(property)
// 将字典转换为字符串
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)
}
/// 定义属性保存授权模型
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.判断是否已经加载过了
if UserAccount.account != nil{
NJLog("已经有加载过")
// 直接返回
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.时间处理
/// 从授权那一刻开始, 多少秒之后过期时间
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.获取用户数据
/// 获取用户信息
func loadUserInfo(finished: (account: UserAccount?, error: NSError?)->())
{
// 断言
// 断定access_token一定是不等于nil的, 如果运行的时access_token等于nil, 那么程序就会崩溃, 并且报错
assert(access_token != nil, "使用该方法必须先授权")
// 1.准备请求路径
let path = "2/users/show.json"
// 2.准备请求参数
let parameters = ["access_token": access_token!, "uid": uid!]
// 3.发送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.保存授权信息
// self.saveAccount()
finished(account: self, error: nil)
}) { (task, error) -> Void in
finished(account: nil, error: error)
}
}
8.新版本特性
import UIKit
import SnapKit
class NewfeatureViewController: UIViewController {
/// 新特性界面的个数
private var maxCount = 4
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
extension NewfeatureViewController: UICollectionViewDataSource
{
// 1.告诉系统有多少组
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
// 2.告诉系统每组多少行
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return maxCount
}
// 3.告诉系统每行显示什么内容
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.设置数据
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.根据指定的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
// 执行放大动画
/*
第一个参数: 动画时间
第二个参数: 延迟时间
第三个参数: 震幅 0.0~1.0, 值越小震动越列害
第四个参数: 加速度, 值越大震动越列害
第五个参数: 动画附加属性
第六个参数: 执行动画的block
第七个参数: 执行完毕后回调的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: - 内部控制方法
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
}