5.新浪微博Swift项目第五天

第五天

昨天我们主要体验了access_token的作用,今天我们将学习怎么从新浪官方获取access_token

今天我们需要用到昨天从官方得到的AppkeyAppSecret以及RedirectURI

从官方文档我们可以看到,获取access_token有一下流程

token流程

1.新建一个XQWBCommon.swift,保存我们应用的信息

// 应用程序信息
let XQWBAppKey = "xxxxxxx"
let XQWBAppSecret  = "xxxxxxxxxxxxxxxxxxxxxxxx"
let XQWBRedirectURI = "http://baidu.com"
// 登录通知
let XQWBUserShouldLoginNotification = "XQWBUserShouldLoginNotification"
// 登录成功
let XQWBUserLoginSuccessNotification = "XQWBUserLoginSuccessNotification"

2.获取access_token需要用户登录并且授权,当用户点击登录按钮,或者access_token过期的时候,使用通知,让用户进行登录

  • 2.1 注册通知

      NotificationCenter.default.addObserver(self, 
    						   selector: #selector(userLogin), 
    	 name: NSNotification.Name(rawValue: XQWBUserShouldLoginNotification),
    	 						 object: nil)
    
  • 2.2 注册通知以后,要记得注销通知

       deinit {
          NotificationCenter.default.removeObserver(self)
      }
    
  • 2.3 实现通知方法

     // 登录
      @objc func userLogin(n: Notification) {
    
      }
    
  • 2.3 在需要登录的时候响应通知

      NotificationCenter.default.post(name: NSNotification.Name(XQWBUserShouldLoginNotification), object: nil)
    

3.授权登录的页面是新浪官方的登录页面,我们使用一个webView加载就可以

  • 3.1 新建一个XQWBOAuthViewController.swift文件,加载webView

        // 重写 loadView, 将webview设置为controller的view
        override func loadView() {
            view = webView
            view.backgroundColor = UIColor.white
            title = "登录新浪微博"
            // 设置返回按钮
            navigationItem.leftBarButtonItem = UIBarButtonItem(title: "返回", target: self, action: #selector(close), isBackButton: true)
        }
    	// 实现返回方法        
       @objc func close() {
           dismiss(animated: true, completion:nil)
        }
    
  • 3.2 在viewDidload()中加载授权页面

        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            webView.delegate = self
            // 加载授权页面
            let urlString = "https://api.weibo.com/oauth2/authorize?client_id=\(XQWBAppKey)&redirect_uri=\(XQWBRedirectURI)"
            // 1. 确定URL
            guard let url = URL(string: urlString) else { return }
            // 2. 请求
            let request = URLRequest(url: url)
            // 3. 加载请求
            webView.loadRequest(request)
        }
    
  • 3.3 实现UIWebViewDelegate方法,在页面开始加载的时候,拿到我们在文章开头分析的code,并且获取access_token

    /// webView将要加载请求
    ///
    /// - Parameters:
    ///   - webView: webview
    ///   - request: 要加载的请求
    ///   - navigationType: 导航类型
    /// - Returns: 是否加载request
    func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
    
    }
    
    • 因为上面的代理方法中可以得到URLRequest,这个方法返回结果为是否加载request,我们可以通过URLRequest进行判断,先写一个伪代码分析
    // 1.如果URLRequest的absoluteString包含我们的`XQWBRedirectURI`,返回true
    // 2.如果URLRequest的query包含`code`,返回true
    // 3.如果都成功,我们就可以拿到`code`
    // 4.拿到`code`我们就可以换取`access_token`即登录成功
    
    • 具体代码如下:
     // 判断是否授权成功
          if request.url?.absoluteString.hasPrefix(XQWBRedirectURI) == false {
              return true
          }
          if request.url?.query?.hasPrefix("code") == false {
              // 取消授权 
              close()
              return false
          }
          // 获取code
          let code = request.url?.query?.substring(from: "code=".endIndex) ?? ""
          // 发起网络请求
          // 获取accessToken
          // 获取成功以后,发送登录的通知
          return false
    
  • 3.4 封装单独的方法,用来在得到code以后换取access_token

      /// 加载token
      ///
      /// - Parameters:
      ///   - code: 授权码
      ///   - completion: 完成回调
      func loadAccessToken(code: String, completion: @escaping (_ isSuccess:Bool)->()) {
          let urlString = "https://api.weibo.com/oauth2/access_token"
          let params = ["client_id":XQWBAppKey,
                        "client_secret":XQWBAppSecret,
                        "grant_type":"authorization_code",
                        "code":code,
                        "redirect_uri":XQWBRedirectURI]
          request(method: .POST, URLString: urlString, parameters: params) { (json, isSuccess) in
              // json就是我们获取到的用户信息    
              // 将用户信息保存到沙盒        
          }
      }
    
  • 3.5 当获取到用户信息以后,我们将用户信息保存到沙盒,每次进入的时候,检测沙盒,就可以判断是否登录,从而展示不同的页面,为此,我们建立一个用户模型XQWBUserAccount

    • 声明相关属性
    	var access_token:String? //= "2.00PsIxFDFLz_lCe099af3af15lX_dB"
    	var uid:String?
    	var expires_in:TimeInterval = 0 {
    	        didSet {
    	            expiresDate = Date(timeIntervalSinceNow: expires_in)
    	        }
    	} // 过期日期  
    	// 过期日期
    	var expiresDate: Date?
    
    • 重写init()方法,从沙盒读取文件
    	override init() {
                super.init()
                // 1. 从磁盘加载保存的文件,
                guard let path = accountFile.cz_appendDocumentDir(),
                let data = NSData(contentsOfFile: path),
                let dict = try?JSONSerialization.jsonObject(with: data as Data, options: []) as? [String:Any] else {
                return
            }
            // 2. 使用字典设置属性值
            yy_modelSet(with: dict ?? [:])
            // 3. 判断token是否过期
            if expiresDate?.compare(Date()) != .orderedDescending {
             print("token过期")
                    // 清空token 
                access_token = nil;
                uid = nil;
                // 删除文件
                _ = try?FileManager.default.removeItem(atPath: path)
            }
    
    
    • 声明一个方法,将登录成功以后的数据保存到沙盒
    	func saveAccount() {
            // 1.模型转字典
            var dict = (self.yy_modelToJSONObject() as? [String:Any]) ?? [:]
            // 2.字典序列化
            dict.removeValue(forKey: "expires_in")
            guard  let data = try? JSONSerialization.data(withJSONObject: dict, options: []),
            let fileName = accountFile.cz_appendDocumentDir() else {
                return
            }
            // 3.写入磁盘
            (data as NSData).write(toFile: fileName, atomically: true)
            print("写入磁盘:\(fileName)")
        }
    

4.注册登录成功的通知,当登陆成功以后,重新加载首页

  • 4.1 注册通知
NotificationCenter.default.addObserver(self, 
						   selector: #selector(loginSuccess), 
						       name: NSNotification.Name(rawValue: XQWBUserLoginSuccessNotification), 
						     object: nil)
  • 4.2 同样,要注销通知
deinit {
     NotificationCenter.default.removeObserver(self)
 }
  • 4.3 实现通知的方法
 @objc func loginSuccess(n: Notification) {
     // print("登录成功")
     // 当view = nil 的时候  会执行 loadView() -> ViewDidload()
     // 清除nav左右按钮
     navItem.leftBarButtonItem = nil
     navItem.rightBarButtonItem = nil
     view = nil
     //注销通知 (避免ViewDidload()中重复注册)
     NotificationCenter.default.removeObserver(self)
 }

PS: 获取access_token就告一段落了,下边我们就做一个启动页和欢迎页面

5.从服务器获取登录用户的信息,设置首页title和欢迎页面的icon

  • 5.1 封装请求用户信息的网络请求方法

    	func loadUserInfo(completion:@escaping (_ dict: [String:Any])->()) {
        guard let uid = userAccount.uid else {
            return
        }
        let urlStr = "https://api.weibo.com/2/users/show.json"
        let params = ["uid":uid]        
        // 发起请求
        tokenRequest(URLString: urlStr, parameters: params) { (json, isSuccess) in
            completion(json as? [String:Any] ?? [:])
        }
    }
    
  • 5.2 首页title按钮的文字在左边,图片在右边,我们可以封装一个单独的方法,来生成这样的按钮

    • 重载构造函数
    // title 是nil ,显示首页,不是的话显示箭头和title
    init(title:String?) {
        super.init(frame: CGRect())
        // 1. 判断title是否为nil
        if title == nil {
            setTitle("首页", for: .normal)
        }else {
            setTitle(title! + " ", for: .normal)
            // 设置图片
            setImage(UIImage(named: "navigationbar_arrow_down"), for: .normal)
            setImage(UIImage(named: "navigationbar_arrow_up"), for: .selected)
        }
        // 2.设置字体颜色
        titleLabel?.font = UIFont.boldSystemFont(ofSize: 17)
        setTitleColor(UIColor.darkGray, for: .normal)  
        // 3.设置大小
        sizeToFit()
    }
    
    • 重新布局子视图
    override func layoutSubviews() {
        super.layoutSubviews()
        guard let titleLabel = titleLabel,
            let imageView = imageView else {
            return
        }
        // 这个方法很关键
        // 将 label 的 x 向左移动 imageView 的宽度
        titleLabel.frame.origin.x = 0
        // 将 imageView 的 x 向右移动 label 的宽度
        imageView.frame.origin.x = titleLabel.bounds.width
    }
    
  • 5.3 创建欢迎页面代码文件和xib文件
    输入图片说明

    • 声明一个类方法,创建xib文件
    class func welcomeView()-> XQWBWelcomeView {
        let nib = UINib(nibName: "XQWBWelcomeView", bundle: nil)
        let v = nib.instantiate(withOwner: nil, options: nil)[0] as! XQWBWelcomeView
        v.frame = UIScreen.main.bounds
        return v
    }
    
    • awakeFromNib()中设置界面
    	override func awakeFromNib() {
        // 1. url
        guard let urlString = XQWBNetWorkManager.shared.userAccount.avatar_large,
            let url = URL(string: urlString) else {
            return
        }
        // 设置头衔
        iconView.sd_setImage(with: url, placeholderImage: UIImage(named: "avatar_default_big"))
        // 设置圆角
        iconView.layer.cornerRadius = iconView.bounds.width * 0.5
        iconView.layer.masksToBounds = true
    }
    
    • didMoveToWindow()中做头像的动画处理
    	// 视图被添加到window,做动画处理
    override func didMoveToWindow() {
        super.didMoveToWindow()
        self.layoutIfNeeded()	// 这里要先做一次更新约束,否则动画中我们会看到背景图片由小变大的bug
        bottomCons.constant = bounds.size.height - 200
        UIView.animate(withDuration: 1.0,
                       delay: 0.7,
                       usingSpringWithDamping: 0.7,
                       initialSpringVelocity: 0,
                       options: [],
                       animations: { 
                    // 更新约束
                    self.layoutIfNeeded()
        }) { (_) in
            UIView.animate(withDuration: 1.0, animations: {
                self.tipLabel.alpha = 1
            }, completion: { (_) in
                self.removeFromSuperview()
            })
        }
    }
    
  • 5.4 创建新特性页面代码文件和xib文件
    输入图片说明

    • 声明一个类方法,创建xib文件
    let nib = UINib(nibName: "XQWBNewFeatureView", bundle: nil)
        let v = nib.instantiate(withOwner: nil, options: nil)[0] as! XQWBNewFeatureView
        v.frame = UIScreen.main.bounds
        return v
    
    • awakeFromNib()中设置界面
    override func awakeFromNib() {
         // 如果使用自动布局设置的界面,从xib加载默认的大小是(600*600)
         // 添加4个图像视图
         let count = 4
         let rect = UIScreen.main.bounds
         for i in 0..<count {
             let imageName = "new_feature_\(i+1)"
             let iv = UIImageView(image: UIImage(named: imageName))
             iv.frame = rect.offsetBy(dx: CGFloat(i) * rect.width, dy: 0)
             scrollView.addSubview(iv)
         }
         // 设置scrollView
         scrollView.contentSize = CGSize(width: CGFloat(count + 1) * rect.width, height: rect.height)
         scrollView.bounces = false
         scrollView.isPagingEnabled = true
         scrollView.showsVerticalScrollIndicator = false
         scrollView.showsHorizontalScrollIndicator = false
         scrollView.delegate = self
         // 隐藏按钮
         enterButton.isHidden = true
     }
    
    • UIScrollViewDelegate代理方法中,处理enterButtonpageControl
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        // 1. scrollview 滚动到最后一屏幕,让视图删除
        let page = Int(scrollView.contentOffset.x / scrollView.bounds.width)
        // 2. 判断是否是最后一页
        if page == scrollView.subviews.count {
            removeFromSuperview()
        }
        // 3. 如果是倒数第二页,显示按钮
        enterButton.isHidden = (page != scrollView.subviews.count - 1)
    }
    func scrollViewDidScroll(_ scrollView: UIScrollView) {      
        // 0.滚动的时候隐藏按钮
        enterButton.isHidden = true       
        // 1. 计算当前偏移量
        let page = Int(scrollView.contentOffset.x / scrollView.bounds.width + 0.5)
        print(page)
        // 2. 设置pageControl
        pageControl.currentPage = page
        // 3. pageControl 的隐藏
        pageControl.isHidden = (page == scrollView.subviews.count)
    }
    
  • 5.5 在XQWBMainViewController中获取版本号,如果是新的版本,就显示新特性,否则就显示欢迎页面

       fileprivate func setupNewFeatureViews() {       
           // 0. 判断是否登录
           if !XQWBNetWorkManager.shared.userLogon {
               return
           }
           // 1.检查版本        
           // 如果更新,显示新特性否则显示欢迎
           let v = isNewVersion ? XQWBNewFeatureView.newFeatureView() : XQWBWelcomeView.welcomeView()
           view.addSubview(v)
       }    
       // 在extension 中 不能定义存储属性,但是,可以声明计算型属性,因为计算型属性
       private var isNewVersion: Bool {
           // 1.取版本号
           //print(Bundle.main.infoDictionary)
           let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
           // 2.取保存在Document() 中的版本号
           let path:String = ("version" as NSString).cz_appendDocumentDir()
           let sandboxVersion = (try?String(contentsOfFile: path)) ?? ""
           // 3.将当前版本保存在沙盒
           _ = try?currentVersion.write(toFile: path, atomically: true, encoding: .utf8)
           // 4.返回是否一致
           return currentVersion == sandboxVersion       
       }
    }
    

总结

通过登录处理登录,获取了code,换取了access_token, 处理了登录以后用户数据,并且做了欢迎页面和新特性页面

往期内容: Swift新浪微博项目更新目录

项目git地址

转载于:https://my.oschina.net/ozawa4865/blog/828008

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值