swift动画 —— 表情弹窗(二)

235 阅读3分钟

效果:

屏幕录制2021-10-08 上午10.gif

接下来为弹窗View在初始化的地方添加stackView,将stackView的填充方式设为填充满并平均分配,子views之间间距设为8,stackView里面的上下左右margin也设为8,并且添加了2个View作为stackView的子view,这样长按点击的时候方便看到是否添加成功。

let iconsContainerView:UIView = {
        let view = UIView()
        view.backgroundColor = .gray
        
        let redView = UIView()
        redView.backgroundColor = .red
        let blueView = UIView()
        blueView.backgroundColor = .blue
        let arrangedSubviews = [redView,blueView]
        let stackView = UIStackView(arrangedSubviews: arrangedSubviews)
        stackView.distribution = .fillEqually
        let padding:CGFloat = 8
        stackView.spacing = padding
        stackView.layoutMargins = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding)
        stackView.isLayoutMarginsRelativeArrangement = true
        view.frame = CGRect(x: 0, y: 0, width: 200, height: 100)
        stackView.frame = view.frame 
        view.addSubview(stackView)
        return view
    }()

接下来设定想要的icon的高度,然后根据高度算出需要的width,并将值赋值给弹窗View。

    let iconHeight:CGFloat = 50
        let numIcons = CGFloat(stackView.arrangedSubviews.count)
        let width = numIcons * iconHeight + (numIcons + 1) * padding
        view.frame = CGRect(x: 0, y: 0, width: width, height: iconHeight + 2 * padding)

接下来用高阶函数创建一个图片的array,并将其设为stackView的arrangedSubviews,为图片和弹窗View添加圆角,并为弹窗View添加阴影。

  let iconsContainerView:UIView = {
        let view = UIView()
        view.backgroundColor = .gray
        let iconHeight:CGFloat = 50
        
        let images = [UIImage(named: "cry_laugh"),UIImage(named: "angry"),UIImage(named: "cry"),UIImage(named: "surprised"),UIImage(named: "blue_like"),UIImage(named: "red_heart")]
        let arrangedSubviews = images.map { (image) -> UIView in
            let imageView = UIImageView(image: image)
            imageView.layer.cornerRadius = iconHeight / 2
            imageView.isUserInteractionEnabled = true
            return imageView
        }
        
        let stackView = UIStackView(arrangedSubviews: arrangedSubviews)
        stackView.distribution = .fillEqually
        
        let padding:CGFloat = 8
        stackView.spacing = padding
        stackView.layoutMargins = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding)
        stackView.isLayoutMarginsRelativeArrangement = true
       
        let numIcons = CGFloat(stackView.arrangedSubviews.count)
        let width = numIcons * iconHeight + (numIcons + 1) * padding
        view.frame = CGRect(x: 0, y: 0, width: width, height: iconHeight + 2 * padding)
        stackView.frame = view.frame
        view.layer.cornerRadius = view.frame.height / 2
        view.layer.shadowColor = UIColor(white: 0.4, alpha: 0.4).cgColor
        view.layer.shadowRadius =  8
        view.layer.shadowOpacity =  0.5
        view.layer.shadowOffset = CGSize(width: 0, height: 4)
        view.addSubview(stackView)
        return view
    }()

这里图片就添加好了,接下来要添加HitTesting。 在handleLongPress方法里面添加一个gesture.state == .changed 的判断,并且获取这个手势传给响应方法

 else if gesture.state == .changed {
            handleGestureChanged(gesture)
        }

在响应方法里面获取手势在弹窗视图中的相应位置,然后判断根据hitTest来获取view,如果是UIImageView就将其alpha设为0,这样方便来观察hitTest的效果。这里后运行后可以发现,如果在长按电时候移动到icon上面,那么icon就会消失。

  @objc func handleGestureChanged(_ gesture:UILongPressGestureRecognizer){
       let pressedLocation = gesture.location(in: iconsContainerView)
       
     let hitTestView = iconsContainerView.hitTest(pressedLocation, with: nil)
       if hitTestView is UIImageView {
           hitTestView?.alpha = 0
       }
   }

接下来就为icon添加animation,这里每次移动到icon上面的时候,icon就会向上移动50,当移动到其他地方的时候,就会移动到原来的地方。

  if hitTestView is UIImageView {
    
           UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
               let stackView = self.iconsContainerView.subviews.first
               stackView?.subviews.forEach({ (imageView) in
                   imageView.transform = .identity
               })
               
               hitTestView?.transform = CGAffineTransform(translationX: 0, y: -50)
           }, completion: nil)
       }

到这里就差不多完成啦,但是还有个问题就是当松开点击的时候,如果松开的地方在icon上面,那么那个icon就不会移动到原来的位置,这时候就需要在手势结束的地方进行处理。这里会先将icon移回原位后在移除弹窗View。

else if gesture.state == .ended {
          // clean up the animation
          UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
              let stackView = self.iconsContainerView.subviews.first
              stackView?.subviews.forEach({ (imageView) in
                  imageView.transform = .identity
              })
              
              self.iconsContainerView.transform = self.iconsContainerView.transform.translatedBy(x: 0, y: 50)
              self.iconsContainerView.alpha = 0
              
          }, completion: { (_) in
              self.iconsContainerView.removeFromSuperview()
          })
      } 

完整代码:

import UIKit

class ViewController: UIViewController {
  
  let iconsContainerView:UIView = {
      let view = UIView()
      view.backgroundColor = .gray
      let iconHeight:CGFloat = 50
      
      let images = [UIImage(named: "cry_laugh"),UIImage(named: "angry"),UIImage(named: "cry"),UIImage(named: "surprised"),UIImage(named: "blue_like"),UIImage(named: "red_heart")]
      let arrangedSubviews = images.map { (image) -> UIView in
          let imageView = UIImageView(image: image)
          imageView.layer.cornerRadius = iconHeight / 2
          imageView.isUserInteractionEnabled = true
          return imageView
      }
      
      let stackView = UIStackView(arrangedSubviews: arrangedSubviews)
      stackView.distribution = .fillEqually
      
      let padding:CGFloat = 8
      stackView.spacing = padding
      stackView.layoutMargins = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding)
      stackView.isLayoutMarginsRelativeArrangement = true
     
      let numIcons = CGFloat(stackView.arrangedSubviews.count)
      let width = numIcons * iconHeight + (numIcons + 1) * padding
      view.frame = CGRect(x: 0, y: 0, width: width, height: iconHeight + 2 * padding)
      stackView.frame = view.frame
      view.layer.cornerRadius = view.frame.height / 2
      view.layer.shadowColor = UIColor(white: 0.4, alpha: 0.4).cgColor
      view.layer.shadowRadius =  8
      view.layer.shadowOpacity =  0.5
      view.layer.shadowOffset = CGSize(width: 0, height: 4)
      view.addSubview(stackView)
      return view
  }()

  override func viewDidLoad() {
      super.viewDidLoad()
      // Do any additional setup after loading the view.
      setupLongPressGesture()
  }
  
  func setupLongPressGesture() {
      view.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)))
  }
  
  fileprivate func handleBegan(_ gesture: UILongPressGestureRecognizer) {
      view.addSubview(iconsContainerView)
      let pressedLocation = gesture.location(in: view)
      let centeredX = (view.frame.width -  iconsContainerView.frame.width) / 2
      iconsContainerView.transform = CGAffineTransform(translationX: centeredX, y: pressedLocation.y )
      iconsContainerView.alpha = 0
      UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut) {
          self.iconsContainerView.alpha = 1
          self.iconsContainerView.transform = CGAffineTransform(translationX: centeredX, y: pressedLocation.y - self.iconsContainerView.frame.height)
          
      }
  }
  
  @objc func handleGestureChanged(_ gesture:UILongPressGestureRecognizer){
      let pressedLocation = gesture.location(in: iconsContainerView)
      //手势只在弹窗里面生效
    let hitTestView = iconsContainerView.hitTest(pressedLocation, with: nil)
     //手势在整个view里面生效
      //let fixedYLocation = CGPoint(x: pressedLocation.x, y: self.iconsContainerView.frame.height / 2)
     // let hitTestView = iconsContainerView.hitTest(fixedYLocation, with: nil)
      if hitTestView is UIImageView {
   
          UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
              let stackView = self.iconsContainerView.subviews.first
              stackView?.subviews.forEach({ (imageView) in
                  imageView.transform = .identity
              })
              
              hitTestView?.transform = CGAffineTransform(translationX: 0, y: -50)
          }, completion: nil)
      }
  }
  
  @objc func handleLongPress(gesture:UILongPressGestureRecognizer) {
      if gesture.state == .began {
          handleBegan(gesture)
      } else if gesture.state == .ended {
          // clean up the animation
          UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
              let stackView = self.iconsContainerView.subviews.first
              stackView?.subviews.forEach({ (imageView) in
                  imageView.transform = .identity
              })
              
              self.iconsContainerView.transform = self.iconsContainerView.transform.translatedBy(x: 0, y: 50)
              self.iconsContainerView.alpha = 0
              
          }, completion: { (_) in
              self.iconsContainerView.removeFromSuperview()
          })
      } else if gesture.state == .changed {
          handleGestureChanged(gesture)
      }
  }
}