美文网首页iOS开发
iOS HUD (Heads Up Display) 旋转框

iOS HUD (Heads Up Display) 旋转框

作者: _浅墨_ | 来源:发表于2021-05-21 15:23 被阅读0次
    一、隐藏软键盘

    在 viewDidLoad() 添加 gestureRecognizer

    // Hide keyboard
    let gestureRecognizer = UITapGestureRecognizer(
      target: self, 
      action: #selector(hideKeyboard))
    gestureRecognizer.cancelsTouchesInView = false
    tableView.addGestureRecognizer(gestureRecognizer)
    
    @objc func hideKeyboard(
      _ gestureRecognizer: UIGestureRecognizer
    ) {
      let point = gestureRecognizer.location(in: tableView)
      let indexPath = tableView.indexPathForRow(at: point)
    
      if indexPath != nil && indexPath!.section == 0 && 
      indexPath!.row == 0 {
        return
      }
      descriptionTextView.resignFirstResponder()
    }
    

    以上代码确保只有点击 section 0, row 0 之外部分才会隐藏软键盘。

    The HUD (Heads Up Display)

    HUD,是 Heads-Up Display 的缩写。HUD 通常用于像下载文件或执行其它长期任务时显示进度条。

    HUD 是 UIView 的子类,我们可以在其它视图之上添加 HUD。实际上,labels 是添加在 cells 顶部的 view,cells 是被添加到 table view 顶部的 view,而 table view 又是被添加在 navigation controller 的顶部的内容视图。

    创建 HUD view

    HudView.swift:

    import UIKit
    
    class HudView: UIView {
      var text = ""
    
      class func hud(
        inView view: UIView, 
        animated: Bool
      ) -> HudView {
        let hudView = HudView(frame: view.bounds)
        hudView.isOpaque = false
    
        view.addSubview(hudView)
        view.isUserInteractionEnabled = false
    
        hudView.backgroundColor = UIColor(
          red: 1, 
          green: 0, 
          blue: 0, 
          alpha: 0.5)
        return hudView
      }
    
       override func draw(_ rect: CGRect) {
          let boxWidth: CGFloat = 96
          let boxHeight: CGFloat = 96
    
          let boxRect = CGRect(
            x: round((bounds.size.width - boxWidth) / 2),
            y: round((bounds.size.height - boxHeight) / 2),
            width: boxWidth,
            height: boxHeight)
    
          let roundedRect = UIBezierPath(
            roundedRect: boxRect, 
            cornerRadius: 10)
          UIColor(white: 0.3, alpha: 0.8).setFill()
          roundedRect.fill()
    
          // Draw checkmark
          if let image = UIImage(named: "Checkmark") {
               let imagePoint = CGPoint(
                  x: center.x - round(image.size.width / 2),
                  y: center.y - round(image.size.height / 2) - boxHeight / 8)
                  image.draw(at: imagePoint)
            }
    
           // Draw the text
           let attribs = [ 
                NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16),
                NSAttributedString.Key.foregroundColor: UIColor.white
           ]
    
           let textSize = text.size(withAttributes: attribs)
    
           let textPoint = CGPoint(
                 x: center.x - round(textSize.width / 2),
                 y: center.y - round(textSize.height / 2) + boxHeight / 4)                                      
                 text.draw(at: textPoint, withAttributes: attribs)
          }
    }
    

    方法 hud(inView, animated) 被称为便利构造函数。它创建并返回一个新的 HudView 实例。

    使用示例:

    let hudView = HudView.hud(inView: parentView, animated: true)
    

    构造函数通常是一个类方法,即不用实例化对象就可以调用的方法,声明它,以 class func 开头,而不是 func 。

    使用 HUD view
    @IBAction func done() {
      guard let mainView = navigationController?.parent?.view 
      else { return }
      let hudView = HudView.hud(inView: mainView, animated: true)
      hudView.text = "Tagged"
    }
    

    guard let

    是一种语法糖,我们可以这样使用它:

    var mainView: UIView
    if let view = navigationController?.parent?.view {
      mainView = view
    } else {
      return
    }
    

    使用 guard let 这种方式,可以精简代码。

    使用导航控制器父级中的视图 - 导航控制器的父级是 tab bar controller。这样可以保证 HUD 覆盖了 navigation controller 和 tab bar controlle 的查看区域。

    每当 UIKit 希望视图重绘时,都会调用 draw() 方法。

    iOS 中的所有内容都是事件驱动的。除非 UIKit 要求视图自行绘制,否则视图不会在屏幕上绘制任何内容。这意味着我们永远不要自己调用 draw() 。

    如果要重绘视图,则应向 UIKit 发送 setNeedsDisplay() 消息。准备好执行绘图时,UIKit 然后将触发 draw() 。

    let boxRect = CGRect(
      x: round((bounds.size.width - boxWidth) / 2),
      y: round((bounds.size.height - boxHeight) / 2),
      width: boxWidth,
      height: boxHeight)
    

    round() 确保矩形的长宽不为小数,因为这会使图像看起来模糊。

    let roundedRect = UIBezierPath(roundedRect: boxRect, cornerRadius: 10)
    UIColor(white: 0.3, alpha: 0.8).setFill()
    roundedRect.fill()
    

    用 UIBezierPath 绘制带有圆角的矩形,非常方便。

    运行 app,结果如下所示:

    添加动画

    在 HudView.swift 添加如下代码:

     // MARK: - Helper methods
    func show(animated: Bool) {
      if animated {
        // 1
        alpha = 0
        transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
        // 2
        UIView.animate(withDuration: 0.3) {
          // 3
          self.alpha = 1
          self.transform = CGAffineTransform.identity
        }
      }
    }
    

    在动画开始之前设置视图的初始状态。这里,将alpha 设置为0,使视图完全透明。将 scale factor 设置为 1.3 ,这样,视图最初会放大到比正常情况下大 1.3 倍的大小。

    调用 UIView.animate(withDuration:animations:) 方法设置动画。将方法传递给闭包,在闭包中执行动画。闭包是一段内联代码,不会立即执行。UIKit 将在闭包内部设置动画终止时的状态。

    在闭包内部,设置动画完成后的视图状态。将 alpha 设置为 1,让 HudView 完全不透明。我们还可以将变换设置为“identity”变换,将比例恢复为正常。由于此代码使用闭包,因此需要使用 self 来引用 HudView 实例及其属性。这就是关闭的规则。

    将 hud(inView:animated:) 方法更改为在返回之前立即调用 show(animated:):

    class func hud(inView view: UIView, animated: Bool) -> HudView {
      . . .
      hudView.show(animated: animated)    // Add this
      return hudView
    }
    
    优化动画

    iOS 有一种称为“spring”动画的东西,它可以上下反弹,并且在视觉上比普通的旧版动画更有趣。使用起来非常简单。

    修改UIView.animate(withDuration:animations:) 代码:

    UIView.animate(
      withDuration: 0.3, 
      delay: 0, 
      usingSpringWithDamping: 0.7, 
      initialSpringVelocity: 0.5,
      options: [], 
      animations: {
        self.alpha = 1
        self.transform = CGAffineTransform.identity
      }, completion: nil) 
    

    Swift 5.3 引入了多个尾随闭包(这里如animations 和 completion)。但是,由于completion中没有代码,因此无法在此处使用 non-trailing closure 语法 - 除非我们将空闭包传递到 completion 闭包。

    运行该 app 并观看它的反弹动画。效果更好一些!

    处理 navigation

    GCD 是一个非常方便但有些底层的库,可以用于处理异步任务。让 app 在执行一些代码之前等待几秒钟,是异步任务的一个完美示例。

    将以下代码添加到 done() 方法底部:

    let delayInSeconds = 0.6
    DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) {
      self.navigationController?.popViewController(animated: true)
    }
    

    DispatchQueue.main.asyncAfter() 函数将闭包作为其最终参数。在该闭包中,我们告诉导航控制器返回到导航堆栈中的上一个视图控制器。

    DispatchQueue.main.asyncAfter() 在 .now() + delayInSeconds 时间之后执行操作。

    我们发现返回上一页面后,HUD 并没有消失,这是不好的体验。

    我们进行如下优化,将以下方法添加到 HudView.swift:

    func hide() {
      superview?.isUserInteractionEnabled = true
      removeFromSuperview()
    }
    

    返回上一页面之前,调用此新方法隐藏 HUD。

    在 LocationDetailsViewController.swift 中修改 done() 方法:

    DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) {
      hudView.hide()   // Add this line
      self.navigationController?.popViewController(animated: true)
    }
    
    代码精简优化

    我们可以把方法抽取到一个工具类,Functions.swift:

    import Foundation
    
    func afterDelay(_ seconds: Double, run: @escaping () -> Void) {
      DispatchQueue.main.asyncAfter(
        deadline: .now() + seconds, 
        execute: run)
    }
    

    这是一个自由函数,而不是对象内部的方法。因此,可以在代码中的任何位置使用它。

    仔细看 afterDelay() 的第二个参数,一个名为 run 的参数,它的类型是 () -> Void。在 Swift 中表示带参数且无返回值的闭包。

    闭包的类型通常如下所示:

    (parameter list) -> return type
    

    在这种情况下,参数列表和返回值均为空, () 和 Void。这也可以写为 Void -> Void 甚至 () -> (),但是推荐这样写 () -> Void,因为它看起来更像一个函数声明。

    对于不立即执行的 closures,@escaping 注释是必需的。 这样一来,Swift 便知道应该持有该 closures 一段时间。

    回到 LocationDetailsViewController.swift,按如下所示修改done() :

    @IBAction func done() {
      ...
      hudView.text = "Tagged"
      afterDelay(0.6) {
        hudView.hide()
        self.navigationController?.popViewController(animated: true)
      }
    }
    

    通过将令人讨厌的 GCD 内容移至一个新函数 afterDelay() 中,我们抽象了代码,使跟踪变得更加容易。

    编写好的程序就是寻找正确的抽象(abstractions)。

    注意:由于引用导航控制器 (navigation controller) 的代码位于闭包中,因此需要使用 self。在闭包内部,我们始终需要显式使用self。但是,这里并不需要在引用 hudView 的行上也加上 self,这是因为 hudView 是一个局部变量,它仅在 done() 方法内存在。

    源码地址:https://github.com/MFiOSDemos/HudDemos.git

    相关文章

      网友评论

        本文标题:iOS HUD (Heads Up Display) 旋转框

        本文链接:https://www.haomeiwen.com/subject/gabhjltx.html