No3 弹出菜单和二维码

一 显示弹出菜单

  • 点击用户名弹出菜单
// 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(获取登录界面)

        注意点:

        1. appkey不能写错 → (error:invalid_client)
        2. redirect_uri不能写错 →(error:redirect_uri_mismatch)
        3. 整个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")
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值