Search code examples
iosswifttableviewprotocols

How to reuse slide up menu class in any view controller?


I am trying to create a slide up menu, completely defined in a custom class named OldMenu.

It would show every time a user tap a UIBarButtonItem, that is also defined in that class.

So the button, the menu, and the menu animation to show the menu are all defined in this custom class.

For now, I can just access the button from an other class but it doesn't seem to trigger the animation to show the menu.

Any one knows why this code isn't working?

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let oldMenu = OldMenu()
        navigationItem.rightBarButtonItems = [oldMenu.MenuButton]
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

class OldMenu: NSObject, UITableViewDelegate, UITableViewDataSource {
    var tableView: UITableView {
        let tableView = UITableView()
        tableView.isScrollEnabled = true
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "Cell")
        return tableView
    }
    
    var transparentView = UIView()
    
    let height: CGFloat = 250
    
    var MenuButton: UIBarButtonItem {
        let btn = UIBarButtonItem()
        btn.title = "Menu"
        btn.target = self
        btn.action = #selector(onClickMenu)
        return btn
    }
}

extension OldMenu {
    
    @objc func onClickMenu(_ sender: Any) {
        let window = UIApplication.shared.keyWindow
        transparentView.backgroundColor = UIColor.black.withAlphaComponent(0.9)
        transparentView.frame = window?.bounds ?? .zero
        window?.addSubview(transparentView)
        
        let screenSize = UIScreen.main.bounds.size
        tableView.frame = CGRect(x: 0, y: screenSize.height, width: screenSize.width, height: height)
        window?.addSubview(tableView)
        
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onClickTransparentView))
        transparentView.addGestureRecognizer(tapGesture)
        
        transparentView.alpha = 0
        
        UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: .curveEaseInOut, animations: {
            self.transparentView.alpha = 0.5
            self.tableView.frame = CGRect(x: 0, y: screenSize.height - self.height, width: screenSize.width, height: self.height)
        }, completion: nil)
        
    }
    
    @objc func onClickTransparentView() {
        let screenSize = UIScreen.main.bounds.size

        UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: .curveEaseInOut, animations: {
            self.transparentView.alpha = 0
            self.tableView.frame = CGRect(x: 0, y: screenSize.height, width: screenSize.width, height: self.height)
        }, completion: nil)
    }
}

extension OldMenu {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        return UITableViewCell()
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 50
    }
    
}

Solution

  • Code

    class ViewController: UIViewController {
    
    var oldMenu: OldMenu!
    
       override func viewDidLoad() {
          super.viewDidLoad()
        
           oldMenu = OldMenu(controller: self)
           navigationItem.rightBarButtonItem = oldMenu.MenuButton
       }
    
       override func didReceiveMemoryWarning() {
          super.didReceiveMemoryWarning()
          // Dispose of any resources that can be recreated.
       }
    }
    
    extension ViewController: MenuDelegate {
    
    
       func tableRowDidSelect(_ indexPath: IndexPath) {
        
       }
    }
    

    OldMenu

    class OldMenu: NSObject {
        
        var presenter: UIViewController?
        
        var menuController = MenuViewController()
    
        var MenuButton: UIBarButtonItem!
        
        init(controller: UIViewController?) {
            super.init()
            presenter = controller
            MenuButton = UIBarButtonItem(title: "Menu", style: .plain, target: self, action: #selector(onClickMenu))
        }
        
        @objc func onClickMenu() {
            menuController.modalPresentationStyle = .custom
            menuController.transitioningDelegate = self
            menuController.delegate = presenter as! MenuDelegate
            presenter?.present(menuController, animated: true, completion: nil)
        }
    }
    
    extension OldMenu: UIViewControllerTransitioningDelegate {
        
        func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
            PresentationController(presentedViewController: presented, presenting: presenting)
        }
        
    }
    

    MenuViewController

    Configure your tableView in MenuViewController, and use delegates to communicate back to the main view

    protocol MenuDelegate {
        func tableRowDidSelect(_ indexPath: IndexPath)
    }
    
    // Configure your tableView in MenuViewController
    // Use delegates to communicate back to main view
    class MenuViewController: UIViewController {
        
        var delegate: MenuDelegate?
        
        override func viewDidLoad() {
            view.backgroundColor = .gray
        }
    }
    

    PresentationController

    You can reuse PresentationController to present view controllers in half screen.

    import Foundation
    import UIKit
    
    class PresentationController: UIPresentationController {
    
      let blurEffectView: UIVisualEffectView!
      var tapGestureRecognizer: UITapGestureRecognizer = UITapGestureRecognizer()
      
      override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
          let blurEffect = UIBlurEffect(style: .dark)
          blurEffectView = UIVisualEffectView(effect: blurEffect)
          super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
          tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissController))
          blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
          self.blurEffectView.isUserInteractionEnabled = true
          self.blurEffectView.addGestureRecognizer(tapGestureRecognizer)
      }
      
      override var frameOfPresentedViewInContainerView: CGRect {
    
          // Set your presentation height
          let presentHeight: CGFloat = 300
          
          return CGRect(origin: CGPoint(x: 0, y: self.containerView!.frame.height - presentHeight),
                 size: CGSize(width: self.containerView!.frame.width, height: presentHeight))
      }
    
      override func presentationTransitionWillBegin() {
          self.blurEffectView.alpha = 0
          self.containerView?.addSubview(blurEffectView)
          self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) in
              self.blurEffectView.alpha = 0.5
          }, completion: { (UIViewControllerTransitionCoordinatorContext) in })
      }
      
      override func dismissalTransitionWillBegin() {
          self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) in
              self.blurEffectView.alpha = 0
          }, completion: { (UIViewControllerTransitionCoordinatorContext) in
              self.blurEffectView.removeFromSuperview()
          })
      }
      
      override func containerViewWillLayoutSubviews() {
          super.containerViewWillLayoutSubviews()
          
          // set your corner radius
        presentedView!.roundCorners([.topLeft, .topRight], radius: 18)
      }
    
      override func containerViewDidLayoutSubviews() {
          super.containerViewDidLayoutSubviews()
          presentedView?.frame = frameOfPresentedViewInContainerView
          blurEffectView.frame = containerView!.bounds
      }
    
      @objc func dismissController(){
          self.presentedViewController.dismiss(animated: true, completion: nil)
      }
    }
    
    extension UIView {
      func roundCorners(_ corners: UIRectCorner, radius: CGFloat) {
          let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners,
                                  cornerRadii: CGSize(width: radius, height: radius))
          let mask = CAShapeLayer()
          mask.path = path.cgPath
          layer.mask = mask
      }
    }
    

    Result

    enter image description here