版權聲明:本文爲博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/ab20514/article/details/50434298
一 顯示彈出菜單
- 點擊用戶名彈出菜單
// MARK: - 監聽點擊事件
@objc private func titleBtnClick(titleBtn: XMGTitleButton)
{
// 1.修改標題按鈕箭頭的方向
titleBtn.selected = !titleBtn.selected
// 2.創建菜單
let sb = UIStoryboard(name: "PopoverViewController", bundle: nil)
// 設置轉場保證 modol 出一個新view時,不會蓋住後面的 view
if let menuVC = sb.instantiateInitialViewController()
{
// 2.1設置負責自定義轉場的代理
menuVC.transitioningDelegate = self
// 2.2設置轉場的樣式
menuVC.modalPresentationStyle = UIModalPresentationStyle.Custom
// 3.彈出菜單
presentViewController(menuVC, animated: true, completion: nil)
}
}
- 轉場菜單展示內容(自定義類XMGPresentationController)
class XMGPresentationController: UIPresentationController {
/*
第一個參數:presentedViewController: 被展現的對象
第二個參數:presentingViewController: 發起轉場的對象(在Xcode6中系統傳入的是nil, 在Xcode7中系統傳入的是野指針)
*/
override init(presentedViewController: UIViewController, presentingViewController: UIViewController) {
super.init(presentedViewController: presentedViewController, presentingViewController: presentingViewController)
NJLog(presentedViewController)
// NJLog(presentingViewController)
}
/*
containerView: 所有被展現的內容都放在containerView上
presentedView(): 通過該方法就可以拿到被展現的視圖
*/
override func containerViewWillLayoutSubviews()
{
super.containerViewWillLayoutSubviews()
// 1.添加蒙版
containerView?.insertSubview(cover, atIndex: 0)
// 2.設置蒙版frame
cover.frame = containerView!.bounds
// 3.調整被展現視圖的大小
presentedView()?.frame = CGRect(x: 100, y: 56, width: 200, height: 200)
}
}
- 點擊菜單外區域,菜單消失
- 添加一個大的蒙版
// MARK: - 內部控制方法(消失菜單)
@objc private func coverClick()
{
presentedViewController.dismissViewControllerAnimated(true, completion: nil)
}
// MARK: -懶加載
private lazy var cover: UIButton = {
let customView = UIButton()
customView.backgroundColor = UIColor(white: 0.5, alpha: 0.2)
customView.addTarget(self, action: Selector("coverClick"), forControlEvents: UIControlEvents.TouchUpInside)
return customView
}()
- 轉場動畫代理方法
extension HomeTableViewController: UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning
{
// MARK: - UIViewControllerTransitioningDelegate
/**
該方法用於返回負責轉場的對象
*/
func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController?
{
return XMGPresentationController(presentedViewController: presented, presentingViewController: presenting)
}
/**
告訴系統誰來負責轉場如何出現
*/
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning?
{
isPesented = true
return self
}
/**
告訴系統誰來負責轉場如何消失
*/
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?
{
isPesented = false
return self
}
// MARK: - UIViewControllerAnimatedTransitioning
/// 告訴系統轉場動畫出現和消失需要多長時間
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval
{
return 0.5
}
/// 無論轉場動畫出現還是消失都會調用這個方法, 我們需要在這個方法中自定義轉場動畫的樣式
// transitionContext: 上下文, 該上下文中就包含了我們需要的所有數據
func animateTransition(transitionContext: UIViewControllerContextTransitioning)
{
let duration = transitionDuration(transitionContext)
if isPesented
{
NJLog("展現")
// 1.拿到被展現的視圖
// 如果是展現, 那麼我們需要修改toVC, 如果是消失我們需要修改formVC
// 被展現的View
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
// 注意: 一旦自定義轉場這時系統就不會幫我們做任何操作, 包括將需要展示的view添加到containerview上也不會幫我們添加
transitionContext.containerView()?.addSubview(toView)
// 2.控制被展現的視圖如何顯示和消失
// _ 忽略該參數
toView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.0)
toView.transform = CGAffineTransformMakeScale(1.0, 0.0)
UIView.animateWithDuration(duration, animations: { () -> Void in
toView.transform = CGAffineTransformIdentity
}) { (_) -> Void in
// 注意: 如果是自定義轉場, 一定要在動畫執行完畢之後告訴系統動畫已經執行完畢, 否則有可能引發一些未知的錯誤
transitionContext.completeTransition(true)
}
}else
{
NJLog("消失")
let formView = transitionContext.viewForKey(UITransitionContextFromViewKey)
// 注意: 消失動畫一下子就不見了的原因是因爲CGFloat是不準確的
// 想解決這個問題, 只需要將y的CGFloat的值改爲一個非常小得值即可
UIView.animateWithDuration(duration, animations: { () -> Void in
formView?.transform = CGAffineTransformMakeScale(1.0, 0.0001)
}, completion: { (_) -> Void in
// 注意: 如果是自定義轉場, 一定要在動畫執行完畢之後告訴系統動畫已經執行完畢, 否則有可能引發一些未知的錯誤
transitionContext.completeTransition(true)
})
}
}
二 二維碼佈局搭建
- 定義首頁右側導航條
@objc private func rigthBtnClick()
{
// 1.創建二維碼控制器
let sb = UIStoryboard(name: "QRCodeViewController", bundle: nil)
let QRCodeVC = sb.instantiateInitialViewController()!
// 2.展現二維碼控制器
presentViewController(QRCodeVC, animated: true, completion: nil)
}
- 默認選中二維碼 TabBar
// MARK: - 生命週期方法
override func viewDidLoad() {
super.viewDidLoad()
// 1.設置默認選中
customTabbar.selectedItem = customTabbar.items![0]
customTabbar.delegate = self
}
- 衝擊波實現
- 設置約束衝擊波和邊框頂部對齊,修改衝擊波頂部約束可以實現掃描功能
/// 自定義UITabBar
@IBOutlet weak var customTabbar: UITabBar!
/// 衝擊波頂部約束
@IBOutlet weak var scanLineTopCons: NSLayoutConstraint!
/// 容器高度
@IBOutlet weak var containerHeightCons: NSLayoutConstraint!
/// 衝擊波
@IBOutlet weak var scanLineView: UIImageView!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
startAnimation()
}
// MARK: - 內部控制方法
/**
開始衝擊波動畫
*/
private func startAnimation()
{
// 1.初始化衝擊波位置
scanLineTopCons.constant = -containerHeightCons.constant
view.layoutIfNeeded()
// 2.執行動畫
UIView.animateWithDuration(1.0, animations: { () -> Void in
// 告訴系統該動畫需要重複
UIView.setAnimationRepeatCount(MAXFLOAT)
self.scanLineTopCons.constant = self.containerHeightCons.constant
self.view.layoutIfNeeded()
})
}
}
- 二維碼和條形碼切換
extension QRCodeViewController: UITabBarDelegate
{
func tabBar(tabBar: UITabBar, didSelectItem item: UITabBarItem) {
// NJLog(item.tag)
containerHeightCons.constant = (item.tag == 0) ? 300 : 150
view.layoutIfNeeded()
// 先移除圖層上所有的動畫
scanLineView.layer.removeAllAnimations()
// 再重新開啓動畫
startAnimation()
}
}
三 掃描二維碼
// MARK: - 懶加載
/// 創建輸入
private lazy var deviceInput: AVCaptureDeviceInput? = {
let device = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
do{
let input = try AVCaptureDeviceInput(device: device)
return input
}catch
{
return nil
}
}()
/// 創建輸出
private lazy var output: AVCaptureMetadataOutput = AVCaptureMetadataOutput()
/// 創建會話
private lazy var session: AVCaptureSession = AVCaptureSession()
/// 創建預覽圖層
private lazy var previewLayer: AVCaptureVideoPreviewLayer = {
let layer = AVCaptureVideoPreviewLayer(session: self.session)
layer.frame = self.view.bounds
return layer
}()
- 開始掃描
// MARK: - 內部控制方法
private func startScan()
{
// 1.判斷輸入是否可以添加到會話中
if !session.canAddInput(deviceInput)
{
return
}
// 2.判斷輸出是否可以添加到會話中
if !session.canAddOutput(output)
{
return
}
// 3.添加輸入和輸出
session.addInput(deviceInput)
session.addOutput(output)
// 4.設置輸出可以解析的數據類型
// 注意點: 設置輸出對象能夠解析的數據類型, 必須在輸出對象添加到會話之後設置
output.metadataObjectTypes = output.availableMetadataObjectTypes
output.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())
// 5.添加預覽圖層
view.layer.insertSublayer(previewLayer, atIndex: 0)
// 5.開始掃描
session.startRunning()
}
- 實現代理方法
extension QRCodeViewController: AVCaptureMetadataOutputObjectsDelegate
{
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!)
{
if metadataObjects.count > 0
{
NJLog((metadataObjects.last as! AVMetadataMachineReadableCodeObject).stringValue)
}
}
}
四 二維碼描邊
找到二維碼4個點,連線描邊
- bounds
- corners
座標轉換
// MARK: - 掃描數據代理
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
for object in metadataObjects {
let dataObject = previewLayer.transformedMetadataObjectForMetadataObject(object as! AVMetadataObject) as! AVMetadataMachineReadableCodeObject
print(dataObject)
}
}
- 轉換結果
# 轉換前
<AVMetadataMachineReadableCodeObject: 0x170220720,
type="org.iso.QRCode",
bounds={ 0.4,0.4 0.1x0.2 }>
corners { 0.4,0.6 0.5,0.6 0.5,0.4 0.4,0.4 },
time 155921691680958,
stringValue "http://weibo.cn/qr/userinfo?uid=5365823342"
# 轉換後
<AVMetadataMachineReadableCodeObject: 0x170622cc0,
type="org.iso.QRCode",
bounds={ 116.6,224.9 79.5x80.0 }>
corners { 116.6,226.1 117.2,304.4 196.1,304.9 195.7,224.9 },
time 155921691680958,
stringValue "http://weibo.cn/qr/userinfo?uid=5365823342"
轉換的目的是將採集到的座標轉換成能夠識別的座標數值
- 繪製圖層
/// 繪製圖層
lazy var drawLayer: CALayer = {
return CALayer()
}()
- 添加圖層
/// 設置圖層
func setupLayers() {
drawLayer.frame = view.bounds
view.layer.insertSublayer(drawLayer, atIndex: 0)
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
previewLayer.frame = view.bounds
view.layer.insertSublayer(previewLayer, atIndex: 0)
}
注意:一定要用
insertSublayer
,否則會遮擋住TabBar
- 創建路徑 & 繪製條碼形狀
/// 繪製條碼形狀
private func drawCornersShape(dataObject: AVMetadataMachineReadableCodeObject) {
// 判斷數組是否爲空
if dataObject.corners.isEmpty {
return
}
let layer = CAShapeLayer()
layer.lineWidth = 4
layer.strokeColor = UIColor.greenColor().CGColor
layer.fillColor = UIColor.clearColor().CGColor
layer.path = cornersPath(dataObject.corners)
// 添加到繪圖圖層
drawLayer.addSublayer(layer)
}
/// 創建邊線路徑
///
/// -parameter corners: 邊角頂點數組
private func cornersPath(corners: NSArray) -> CGPathRef {
let path = UIBezierPath()
var point = CGPoint()
// 1. 移動到第一個點
var index = 0
CGPointMakeWithDictionaryRepresentation((corners[index++] as! CFDictionaryRef), &point)
path.moveToPoint(point)
// 2. 遍歷剩餘的點
while index < corners.count {
CGPointMakeWithDictionaryRepresentation((corners[index++] as! CFDictionaryRef), &point)
path.addLineToPoint(point)
}
// 3. 關閉路徑
path.closePath()
return path.CGPath
}
注意
*corners
是保存CFDictionary
對象的數組
* 一定要判斷corners
是否包含數據,否則會崩潰
- 清空繪圖圖層
/// 清空繪圖圖層
private func clearDrawLayer() {
if drawLayer.sublayers == nil {
return
}
for layer in drawLayer.sublayers! {
layer.removeFromSuperlayer()
}
}
注意:一定要判斷
subLayers
否則會崩潰
- 調整後的代碼
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
clearDrawLayer()
for object in metadataObjects {
if object is AVMetadataMachineReadableCodeObject {
let dataObject = previewLayer.transformedMetadataObjectForMetadataObject(object as! AVMetadataObject) as! AVMetadataMachineReadableCodeObject
drawCornersShape(dataObject)
print(dataObject.stringValue)
}
}
}
一定要判斷一下 object 的類型,否則遇到非
CodeObject
會直接崩潰
- 二維碼的掃描範圍
/// 創建輸出
private lazy var output: AVCaptureMetadataOutput = {
let output = AVCaptureMetadataOutput()
let containerFrame = self.containerView.frame
let size = UIScreen.mainScreen().bounds.size
// 注意:
// 1.rectOfInterest 接收的都是比例
// 2.rectOfInterest是按照橫屏的左上角爲原點
output.rectOfInterest = CGRect(x: containerFrame.origin.y / size.height, y: containerFrame.origin.x / size.width, width: containerFrame.size.height / size.height, height: containerFrame.size.width / size.width)
return output
}()
- 生成二維碼
/// 生成二維碼
private func generateQRCodeImage() -> UIImage {
// 1. 生成二維碼
let qrFilter = CIFilter(name: "CIQRCodeGenerator")!
qrFilter.setDefaults()
qrFilter.setValue("極客江南".dataUsingEncoding(NSUTF8StringEncoding), forKey: "inputMessage")
let ciImage = qrFilter.outputImage
// 2. 縮放處理
let transform = CGAffineTransformMakeScale(10, 10)
let transformImage = ciImage.imageByApplyingTransform(transform)
// 3. 顏色濾鏡
let colorFilter = CIFilter(name: "CIFalseColor")!
colorFilter.setDefaults()
colorFilter.setValue(transformImage, forKey: "inputImage")
// 前景色
colorFilter.setValue(CIColor(color: UIColor.blackColor()), forKey: "inputColor0")
// 背景色
colorFilter.setValue(CIColor(color: UIColor.whiteColor()), forKey: "inputColor1")
let outputImage = colorFilter.outputImage
return insertAvatarImage(UIImage(CIImage: outputImage), avatar: UIImage(named: "avatar")!)
}
- 插入頭像
func insertAvatarImage(qrimage: UIImage, avatar: UIImage) -> UIImage {
UIGraphicsBeginImageContext(qrimage.size)
let rect = CGRect(origin: CGPointZero, size: qrimage.size)
qrimage.drawInRect(rect)
let w = rect.width * 0.2
let x = (rect.width - w) * 0.5
let y = (rect.height - w) * 0.5
avatar.drawInRect(CGRect(x: x, y: y, width: w, height: w))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
五 OAuth授權
OAuth授權
- 可以讓第三方客戶端在不知道用戶的賬號密碼的前提下授權
新浪OAuth授權步驟
- 1>成爲新浪開發者
- open.weibo.com → 註冊一個賬號
- 2>在新浪開發者平臺創建一個應用程序
- http://open.weibo.com/apps/new?sort=mobile
- 拿到App Key和App Secret
3>獲取授權
3.1 獲取未授權的RequestToken(獲取登錄界面)
注意點:
- appkey不能寫錯 → (error:invalid_client)
- redirect_uri不能寫錯 →(error:redirect_uri_mismatch)
- 整個URL中不能出現空格 →(error:invalid_request)
3.2 獲取已經授權的RequestToken(讓用戶登錄)
- 只要用戶登錄成功, 就會自動跳轉到回調頁面
- 在回調界面地址的後面會跟上一個code= , code=後面的字符串就是已經授權的RequestToken
- 如果取消授權也會調整到回調界面, 但是URL後面沒有code=
- 只要用戶登錄成功, 就會自動跳轉到回調頁面
3.3利用已經授權的RequestToken換取AccessToekn
加載授權頁面
- 準備工作
- 新建
OAuth
文件夾 - 新建
OAuthViewController.swift
繼承自UIViewController
- 新建
加載 OAuth 視圖控制器
修改
BaseTableViewController
中用戶登錄部分代碼/// 用戶登錄 func visitorLoginButtonClicked() { let oauth = OAuthViewController() let nav = UINavigationController(rootViewController: oauth) presentViewController(nav, animated: true, completion: nil) }
在
OAuthViewController
中添加以下代碼lazy var webView: UIWebView = { return UIWebView() }() override func loadView() { view = webView title = "XXX微博" navigationItem.rightBarButtonItem = UIBarButtonItem(title: "關閉", style: UIBarButtonItemStyle.Plain, target: self, action: "close") } /// 關閉 func close() { dismissViewControllerAnimated(true, completion: nil) }
- 準備工作
獲取已經授權的RequestToken
/*
webView的該代理方法用於控制是否允許發起請求
*/
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
/*
授權成功: http://www.520it.com/?code=7fd52d029ec1578775d3abec426e878a
授權失敗: http://www.520it.com/?error_uri=%2Foauth2%2Fauthorize&error=access_denied&error_description=user%20denied%20your%20request.&error_code=21330
其它界面: https://api.weibo.com/oauth2/ 開頭
*/
NJLog(request.URL!)
// 1.判斷是否是授權回調頁面
guard let urlStr = request.URL?.absoluteString where urlStr.hasPrefix("http://www.520it.com") else
{
return true
}
// 2.判斷是否授權成功
guard !urlStr.containsString("?error_uri=") else
{
SVProgressHUD.showErrorWithStatus("授權失敗", maskType: SVProgressHUDMaskType.Black)
return false
}
// 3.授權成功, 截取code=後面的內容
if let str = request.URL?.query
{
// 3.1截取code=後面的字符串
// let code = (str as NSString).substringFromIndex("code=".characters.count)
let code = str.substringFromIndex("code=".endIndex)
NJLog(code)
}
return false
}
- 獲取AccessToken
/**
根據Code換取AccessToken
- parameter code: 已經授權的RequestToken
*/
private func loadAccessToken(code: String)
{
let path = "oauth2/access_token"
// 1.拼接參數
let parameters = ["client_id": WB_App_Key, "client_secret": WB_App_Secret, "grant_type": "authorization_code", "code": code, "redirect_uri": WB_Redirect_URI]
// 2.發送請求
NetworkTools.shareInstance.POST(path, parameters: parameters, success: { (_, dict) -> Void in
// "access_token" = "2.00SqDL_C04G7Sw87a2e20ec9819trD";
NJLog(dict)
}) { (_, error) -> Void in
NJLog(error)
}
}
- 保存AccessToken,UserAccountModel
class UserAccountModel: NSObject, NSCoding {
var access_token: String?
var expires_in: NSNumber?
var uid: String?
// MAKR: - 生命週期方法
init(dict: [String: AnyObject])
{
super.init()
setValuesForKeysWithDictionary(dict)
}
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 saveUserAccount() -> Bool
{
let docPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).last!
let filePath = (docPath as NSString).stringByAppendingPathComponent("useraccount.plist")
NJLog("filePath = \(filePath)")
return NSKeyedArchiver.archiveRootObject(self, toFile: filePath)
}
- 歸檔
// MARK: - NSCoding
// 將對象寫入到文件中
required init?(coder aDecoder: NSCoder) {
access_token = aDecoder.decodeObjectForKey("access_token") as? String
expires_in = aDecoder.decodeObjectForKey("expires_in") as? NSNumber
uid = aDecoder.decodeObjectForKey("uid") as? String
}
// 從文件中讀取對象
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(access_token, forKey: "access_token")
aCoder.encodeObject(expires_in, forKey: "expires_in")
aCoder.encodeObject(uid, forKey: "uid")
}