Harry Blue
Harry Blue

Reputation: 4522

Present a Modal from the View Model of a UITableViewController

I have a UITableViewController, that as a ViewModel class. I am trying to build out my application using the MVVM pattern.

My tableView has a cell that display an image, that image has a gesture recogniser that calls a method in the view model on press.

At this point I would like to present a ViewController modally, with some embedded content.

However my TableView cell conforms to UITableViewCell so I cannot call present from here.

My ViewModel does not conform to anything, so I cannot call present from there either.

How can I trigger a modal to appear, from within a UITableViewCell?

Upvotes: 1

Views: 1597

Answers (2)

Raj Sharma
Raj Sharma

Reputation: 1147

Add a UIWindow extension

extension UIWindow {

static var top: UIViewController? {
    get {
        return topViewController()
    }
}

static var root: UIViewController? {
    get {
        return UIApplication.shared.delegate?.window??.rootViewController
    }
}

static func topViewController(from viewController: UIViewController? = UIWindow.root) -> UIViewController? {
    if let tabBarViewController = viewController as? UITabBarController {
        return topViewController(from: tabBarViewController.selectedViewController)
    } else if let navigationController = viewController as? UINavigationController {
        return topViewController(from: navigationController.visibleViewController)
    } else if let presentedViewController = viewController?.presentedViewController {
        return topViewController(from: presentedViewController)
    } else {
        return viewController
    }
}

}

than call this from anywhere like:

    guard let topController = UIWindow.top else  { return } // UIWindow.root
    let youVC = theStoryboard.instantiateViewController(withIdentifier: "YourViewController") as! YourViewController
    youVC.modalTransitionStyle = .crossDissolve
    youVC.modalPresentationStyle = .overCurrentContext
    topController.present(youVC, animated: true, completion: nil)

Upvotes: 1

Martin Pilch
Martin Pilch

Reputation: 3305

You have couple of options but I will cover solution with delegate.

The idea is to define protocol and property of that protocol in MyViewModel and make MyViewController conforming to it.

Here is how the MyViewModel could look like:

protocol MyViewModelDelegate: class {
    func didTapOnCell()
}

class MyViewModel {
    // Please note the delegate is weak and optional
    weak var delegate: MyViewModelDelegate?

    // This function handle gesture recognizer taps
    @objc func handleImageViewTap() {
        delegate?.didTapOnCell()
    }

    // Here is the rest of the ViewModel class...
}

Then in the MyViewController you set viewModel's delegate property to self and conforms to the protocol function (I'm assuming view controller references the view model instance).

class MyViewController: UITableViewController {

    func setup() {
        // ...
        // When MyViewModel is initialised, set the delegate property to self
        myViewModel.delegate = self
    }
}

extension MyViewController: ViewModelDelegate {
    func didTapOnCell() {
        // ...
        // Allocate instance of anotherViewController here and present it
        self.present(anotherViewController, animated: true, completion: .none)
    }
}

This way you can let know MyViewController something happened in MyViewModel and act accordingly.

Please note it's necessary to make delegate property optional to avoid retain cycles.

Upvotes: 1

Related Questions