Search code examples
iosviewcontrollerswift5

Extension UIViewController - custom transition , problem with @objc func parametr


I want to create an extension for UIViewController but I have one problem with my @objc func.

How to pass this parameter below?

#selector(handlePanGestExt(sender: **cardOriginY:** )))

    fileprivate var cardOriginY : CGFloat!
    extension UIViewController {
    
    func panGestureRecognizerToHandleDragAndDissmisView(inCardView : UIView, cardOriginY : CGFloat) {
        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGestExt(sender: cardOriginY: )))
        inCardView.addGestureRecognizer(panGesture)
    }
    
    @objc func handlePanGestExt(sender: UIPanGestureRecognizer, cardOriginY : CGFloat) {
        let fileView = sender.view!
        
        switch sender.state {
        case .began, .changed:
            moveViewWithPan(view: fileView, sender: sender)
        case .ended:
            let dragVelocity = sender.velocity(in: view)
            if dragVelocity.y >= 1300 {
                dismiss(animated: false, completion: nil)
            } else {
                returnViewToOrigin(view: fileView, cardOriginY: cardOriginY)
            }
        default:
            break
        }
    }
    func moveViewWithPan(view: UIView, sender: UIPanGestureRecognizer) {
        let translation = sender.translation(in: view)
        guard translation.y >= 0 else { return }
        view.center = CGPoint(x:view.center.x, y: view.center.y + translation.y)
        sender.setTranslation(CGPoint.zero, in: view)
    }
    func returnViewToOrigin(view: UIView, cardOriginY : CGFloat) {
        UIView.animate(withDuration: 0.3) {
            view.frame.origin = CGPoint(x: 0.0 , y: cardOriginY)
        }
    }

what am I doing wrong ? When I am calling func panGestureRecognizerToHandleDragAndDissmisView, I want to pass this parameter cardOriginY


Solution

  • UIPanGestureRecognizer's selector can take only one argument and it's UIPanGestureRecognizer itself. Basically it should look like this

    @objc func handlePanGestExt(_ sender: UIPanGestureRecognizer) {
       // your code here
    }
    

    In your concrete situation, you need to pass one more argument for your calculations cardOriginY. I can propose you to do the next thing:

    1. Add DismissableCardContainer for reusing ability
    2. Add PanGestureActionHandler to avoid unnecessary complications with @objc modifiers
    3. Implement DismissableCardContainer protocol for your purpose

    Here's the code:

    protocol DismissableCardContainer: AnyObject {
          func addPanGestureRecognizerToHandleDragAndDissmis(
            to cardView : UIView,
            cardOriginY : CGFloat
          )
      }
    
    extension DismissableCardContainer where Self: UIViewController {
      
      func addPanGestureRecognizerToHandleDragAndDissmis(
        to cardView : UIView,
        cardOriginY : CGFloat
      ) {
        let panGesture = PanGestureActionHandler(
          action: { [weak self] gesture in
            self?.handlePanGestExt(
              gesture,
              cardOriginY
            )
          }
        )
        
        cardView.addGestureRecognizer(panGesture)
      }
      
      private func handlePanGestExt(
        _ sender: UIPanGestureRecognizer,
        _ cardOriginY: CGFloat
      ) {
        let fileView = sender.view!
        
        switch sender.state {
        case .began, .changed:
          moveViewWithPan(view: fileView, sender: sender)
        case .ended:
          let dragVelocity = sender.velocity(in: view)
          if dragVelocity.y >= 1300 {
            dismiss(animated: false, completion: nil)
          } else {
            returnViewToOrigin(view: fileView, cardOriginY: cardOriginY)
          }
        default:
          break
        }
      }
      
      private func moveViewWithPan(view: UIView, sender: UIPanGestureRecognizer) {
        let translation = sender.translation(in: view)
        guard translation.y >= 0 else { return }
        view.center = CGPoint(x:view.center.x, y: view.center.y + translation.y)
        sender.setTranslation(CGPoint.zero, in: view)
      }
      
      private func returnViewToOrigin(view: UIView, cardOriginY : CGFloat) {
        UIView.animate(withDuration: 0.3) {
          view.frame.origin = CGPoint(x: 0.0 , y: cardOriginY)
        }
      }
    }
    
    final class PanGestureActionHandler: UIPanGestureRecognizer {
      
      typealias Callback = ((PanGestureActionHandler) -> Void)
      
      private let action: PanGestureActionHandler.Callback
      
      init(
        action: @escaping PanGestureActionHandler.Callback
      ) {
        self.action = action
        
        super.init(target: nil, action: nil)
        
        addTarget(
          self,
          action: #selector(self.handleGestureAction)
        )
      }
      
      @objc private func handleGestureAction(_ gr: UIPanGestureRecognizer) {
        action(gr as! PanGestureActionHandler)
      }
    }
    

    After that there's only left to conform DismissableCardContainer protocol where you need it 🙂

    final class YourViewController: UIViewController, DismissableCardContainer {
    // Your code here
    }