Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding a UIViewController inside a UICollectionView Cell

Tags:

ios

swift

How would I go about inserting a UIViewController inside a UICollectionView Cell?

like image 673
Tyler Rutt Avatar asked Feb 28 '17 00:02

Tyler Rutt


4 Answers

You will need to make a custom container view controller: https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html

func display(contentController content: UIViewController, on view: UIView) {
    self.addChildViewController(content)
    content.view.frame = view.bounds
    view.addSubview(content.view)
    content.didMove(toParentViewController: self)
}

For each cell in your container, you will have to call something like the above function with the correct child view controller on the cell's content view.

Be careful not to try to add multiple view controllers onto the same cell and make sure they get removed properly as well.

This idea is complex enough that it isn't conducive to a simple stack overflow answer but hopefully the above will be enough to get you started.

like image 71
Daniel T. Avatar answered Nov 01 '22 15:11

Daniel T.


It's relatively easy to do this now

Issue 1, with table views, if you only have a small number of cells, you can simply click to make the cells static - and you're done. Unfortunately with collection views, Apple did not add the ability to make them static. So you have to do it the hard way.

1, Figure out how to dynamically add a container view in to a normal scene:

This tutorial: https://stackoverflow.com/a/23403979/294884

perfectly explains how to add container views dynamically (to ordinary scenes).

Scroll down to "dynamically load ..."

enter image description here

2, But cells are not view controllers!

The fact is your cell will have to know about its own boss view controller:

class CrazyBigCell: UICollectionViewCell {
    weak var boss: ThatClass? = nil

when you make the cells, set that "boss" variable...

func collectionView(_ collectionView: UICollectionView,
                cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    
    let cell = collectionView.dequeueReusableCell(
      withReuseIdentifier: "CrazyBigCellID", for: indexPath) as! CrazyBigCell
    
    cell.boss = self
    cell.data = someData[indexPath.row]
    
    return cell
}

3, So install the container view, but using the boss's VC

Follow the tutorial above exactly, but: when it comes to adding the child VC, add it to the boss VC.

class CrazyBigCell: UICollectionViewCell {
    weak var boss: ThatClass? = nil
    @IBOutlet var chatHolder: UIView!
    var chat: Chat? = nil
    ....

and so ... the secret sauce is: normally you would say something like

        self.addChild(chat!)

but instead it is

        boss?.addChild(chat!)

So ...

private func _installChatBlock() {
    if chat == nil {
        print(">>> installing chat for " + name etc)
        
        chat = _sb("Chat") as? Chat
        boss?.addChild(chat!)
        chatHolder.addSubview(chat!.view)
        chat!.view.bindEdgesToSuperview()
        chat!.didMove(toParent: boss)
    }
    else {
        print(">>> already had a Chat for " + name etc)
    }
    
    chat!.data = data  // or whatever
    chat!.loadOurChat() // or whatever
}

Where to call _installChatBlock ?

Very likely, call that where you set the data for this cell

var data: [String : Any] = [:] { // whatever
    didSet {
        headline.text = data["headline.text"] // etc
        name.text = data["name"] // etc
        _installChatBlock()
    }
}

Phew.

like image 45
Fattie Avatar answered Nov 01 '22 14:11

Fattie


What Daniel said is correct, however if you have to add many childViewControllers to a UICollectionViewCell or UITableViewCell, it could be cumbersome to maintain.

Here's a nice post from Suroush Khanlou which handles that complexity and simplifies the process.

http://khanlou.com/2015/04/view-controllers-in-cells/

like image 5
Rameswar Prasad Avatar answered Nov 01 '22 13:11

Rameswar Prasad


Generic approach

protocol ChildControllersManagerDelegate: class {
    associatedtype ViewControllerType: UIViewController
    func willAdd(_ childViewController: ViewControllerType, at index: Int)
}

final class ChildControllersManager<V, D: ChildControllersManagerDelegate> where D.ViewControllerType == V {
    private var viewControllers = [Int: V]()
    weak var delegate: D?

    func addChild(at index: Int, to viewController: UIViewController, displayIn contentView: UIView) {
        let childVC: V
        if let vc = viewControllers[index] {
            print("Using cached view controller")
            childVC = vc
        } else {
            print("Creating new view controller")
            childVC = V()
            viewControllers[index] = childVC
        }

        delegate?.willAdd(childVC, at: index)

        viewController.addChild(childVC)
        childVC.view.frame = contentView.bounds
        contentView.addSubview(childVC.view)
        childVC.didMove(toParent: viewController)
    }

    func remove(at index: Int) {
        print("Remove at \(index)")
        guard let vc = viewControllers[index] else { return }
        vc.willMove(toParent: nil)
        vc.view.removeFromSuperview()
        vc.removeFromParent()
    }

    func cleanCachedViewControllers(index: Int) {
        let indexesToClean = viewControllers.keys.filter { key in
            key > index + 1 || key < index - 1
        }
        indexesToClean.forEach {
            viewControllers[$0] = nil
        }
    }
}

...

extension ViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        childControllersManager.addChild(at: indexPath.row, to: self, displayIn: cell.contentView)
    }

    func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        childControllersManager.remove(at: indexPath.row)
        childControllersManager.cleanCachedViewControllers(index: indexPath.row)
    }
}

extension ViewController: ChildControllersManagerDelegate {
    typealias ViewControllerType = MyVC

    func willAdd(_ childViewController: MyVC, at index: Int) {
         ...
    }
}
like image 5
Andrey Volobuev Avatar answered Nov 01 '22 13:11

Andrey Volobuev