dank_muffin
dank_muffin

Reputation: 263

How to push a UIViewController programmatically from "didSelectItemAt" in UICollectionView (using separate files)?

I have a UICollectionView set up in a separate swift file that is being shown on HomeViewController. I want to push a DetailViewController when a collection view cell is selected. I want to do this programmatically, without storyboards. I have always pushed ViewControllers with "navigationController.pushViewController(vc, animated: true)" but I cannot access navigationController from within the UICollectionView's "didSelectItemAt" function. If it matters, I do have a custom UICollectionViewCell set up and being used for the cells.

How can I make this work? The collectionView is displaying properly, and when I select a cell, I know that didSelectItemAt is being ran as my print statement "Did select item at..." shows in the console.

After hours of googling I can't figure it out. I'm still learning the M-V-C structure so I may have a fundamental flaw in how everything is setup. I've also seen I might need to create a custom delegate to handle this, but I feel like I'm missing something simpler since the didSelectItemAt function is right there.

Here's the pertinent code from my UICollectionView swift file:

class HomeDataController: NSObject, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

        print("Did select item at...")
        // this is where I am trying to push the DetailViewController

    }
}

Here is my HomeViewController:

class HomeViewController: UIViewController {

    let collectionView: UICollectionView = {
            let layout = UICollectionViewFlowLayout()
            layout.scrollDirection = .vertical
            layout.minimumLineSpacing = 30
            layout.sectionInset.top = 20
            layout.sectionInset.bottom = 20

            let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
            cv.translatesAutoresizingMaskIntoConstraints = false
            cv.backgroundColor = UIColor(named: "background")
            cv.register(HomeCustomCell.self, forCellWithReuseIdentifier: "homeCell")
            return cv
        }()

    let dataController: HomeDataController = {
        let dataController = HomeDataController()
        return dataController
    }()


    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView.dataSource = self.dataController
        collectionView.delegate = self.dataController

        view.addSubview(collectionView)
    }
}

Upvotes: 2

Views: 755

Answers (1)

Pratik Sodha
Pratik Sodha

Reputation: 3727

class HomeViewController: UIViewController {

let collectionView: UICollectionView = {
            let layout = UICollectionViewFlowLayout()
            layout.scrollDirection = .vertical
            layout.minimumLineSpacing = 30
            layout.sectionInset.top = 20
            layout.sectionInset.bottom = 20

            let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
            cv.translatesAutoresizingMaskIntoConstraints = false
            cv.backgroundColor = UIColor(named: "background")
            cv.register(HomeCustomCell.self, forCellWithReuseIdentifier: "homeCell")
            return cv
        }()

    lazy var dataController: HomeDataController = {
        let dataController = HomeDataController(controller: self)
        dataController.didSelectionCompletion = { indexPath in
            print("Called...")
        }
        dataController.delegate = self
        return dataController
    }()


    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView.dataSource = self.dataController
        collectionView.delegate = self.dataController

        view.addSubview(collectionView)
    }
}

//MARK: - HomeDataControllerDelegate 
extension HomeViewController : HomeDataControllerDelegate {
    func homeDataController(_ controller: HomeDataController, didSelectItemAt: IndexPath) {
        print("Called..")
    }
}

HomeDataController.swift

protocol HomeDataControllerDelegate {
    func homeDataController(_ controller : HomeDataController, didSelectItemAt : IndexPath)
}


class HomeDataController: NSObject, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

    private var controller : UIViewController? = nil

    var delegate : HomeDataControllerDelegate? = nil

    var didSelectionCompletion : ((IndexPath) -> (Void))? = nil

    override init() {
        super.init()
    }

    init(controller : UIViewController) {
        super.init()
        self.controller = controller
    }


    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

        print("Did select item at...")
        // this is where I am trying to push the DetailViewController

        /* Method1 - using property*/
        let detialController = UIViewController()
        controller?.navigationController?.pushViewController(detialController, animated: true)

        /* Method2 - delegate */
        delegate?.homeDataController(self, didSelectItemAt: indexPath)

        /* Method3 - Block */
        self.didSelectionCompletion?(indexPath)
    }
}

Using method 1 (using property) there is limitations of that HomeDataController have always push only one detail controller. It's become dependent class. There also option pass destination controller into init method of HomeDataController again it's become more complex. But if you have fix detailViewController then you can use this method. But it's not scalable code for any specific change request.

Blocks and delegates are more standard and scalable options. Using this methods we have full control over UIController and any action.

Hope it's helps to you.

Upvotes: 1

Related Questions