Home > Mobile >  VC with TableView nested in CollectionViewCell, how can i pass data from TableView back to ViewContr
VC with TableView nested in CollectionViewCell, how can i pass data from TableView back to ViewContr

Time:07-30

Within my CharacterSelectionVC I have a TableView that is nested in a CollectionView. Each CollectionViewCell represents a part (arms, eyes, pupils, etc.) of my character. Each tableViewCell represents an alternate image for the character part. To display the part's name, I attached a label to the tableViewCell.

After, selecting a tableView's cell, how can I pass its label data to my CharacterSelectionVC? I tried creating an instance of the view controller in the table view and passing the label input into the instance's property but when i print(mouthSelectionCharacterModel)from CharacterSelectionVC , the array comes back empty. Apologies upfront, if i didn't correctly follow any coding best practices.

Visual Representation of TableView nested in CollectionView enter image description here

CollectionViewCell & TableView:

class CharacterInventoryCVCellFinal: UICollectionViewCell, UITableViewDataSource, UITableViewDelegate{

    var dataArray2: [String]?
    var mouthSelectionFinal: [String] = []
    let characterSelectionVC = CharacterSelectionViewController()   

    @IBOutlet var flexAnimationCustomElements: UILabel!
    
    @IBOutlet var tableView: UITableView!
    
    struct PropertyKeys {
          static let tableViewCellID = "tbCell"
      }
     
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataArray2?.count ?? 0
       }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: PropertyKeys.tableViewCellID, for: indexPath)
            
        cell.textLabel!.text = dataArray2?[indexPath.row]
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
      
        let optionChoice = indexPath.row
        print(optionChoice)
        
        var str8 = dataArray2?[indexPath.item]
        mouthSelectionFinal.append(str8!)
        
        characterSelectionVC.mouthSelectionCharacterModel = mouthSelectionFinal
                         
        print(mouthSelectionFinal)  
        
        }
}

ViewController & CollectionView:

class CharacterSelectionViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

    //MARK: Container for collectionView cell's labels
    var elementsForSelection: [String] = ["mouth",  "head", "hair", "pupils", "eyes", "eyelids", "eyeBrows", "ears", "neck", "torso", "shoulders", "arms", "hands", "nose"]

    //MARK: Container for user selected tableView cell's label
    var mouthSelectionCharacterModel: [String] = []


@IBOutlet var characterElementSelectionCollection: UICollectionView!

@IBAction func touchSelectCharacterBt(_ sender: Any) {
        applyCharacter()
    }


       func  applyCharacter() {
       print(mouthSelectionCharacterModel)

       }

       func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return rawData4.count
       }
    
       func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cellA = collectionView.dequeueReusableCell(withReuseIdentifier: PropertyKeys.collectionViewCellID, for: indexPath) as! CharacterInventoryCVCellFinal
        
        let dataArray = rawData4[indexPath.row]
        cellA.updateCellWith2(row: dataArray)

        cellA.flexAnimationCustomElements.text = self.elementsForSelection[indexPath.item]
        
        return cellA
        
      }
    
     func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
      
        elementChoice = indexPath.item
        print(elementChoice)
        
     }
    
        override func viewDidLoad() {
    
        super.viewDidLoad()        
        
        characterElementSelectionCollection.delegate = self
        characterElementSelectionCollection.dataSource = self
        self.view.addSubview(characterElementSelectionCollection)
        
        let dict =  CloneAnimation_20200907.finalize(imageFileNames: characterSVCImageFileNames)
        
        mouthSelection = dict["mouthAngry"]!
        headSelection = dict["head1"]!
        hairSelection = dict["hair"]!
        pupilsSelection = dict["pupilsLeft"]!
        eyesSelection = dict["eyeLeft"]!
        eyeLidsSelection = dict["eyeLidLeft"]!
        eyeBrowsSelection = dict["eyeBrowRight"]!
        earsSelection = dict["earRight"]!
        neckSelection = dict ["neck"]!
        torsoSelection = dict["torso"]!
        shouldersSelection = dict["shoulderLeft"]!
        armsSelection = dict["armLowerLeft"]!
        handsSelection = dict["handLeft"]!
        noseSelection = dict["nose"]!
        
        rawData4 = [mouthSelection, headSelection, hairSelection, pupilsSelection, eyesSelection, eyeLidsSelection, eyeBrowsSelection, earsSelection, neckSelection, torsoSelection, shouldersSelection, armsSelection, handsSelection, noseSelection]
        
  
    }

}

Extensions

//MARK: To help pass along data for tableView
extension CharacterInventoryCVCellFinal{
    func updateCellWith2(row: [String]) {
        self.dataArray2 = row
        self.tableView.reloadData()
    }
}

CodePudding user response:

Your ViewController is managing the collection view.

Your CollectionViewCell is managing the table view.

So, when you get didSelectRowAt in your CollectionViewCell, it needs to tell the Controller what was selected, and let the controller update the data.

Here's a quick example...

Simple Struct for the data

struct BodyStruct {
    var title: String = ""
    var parts: [String] = []
    var selectedPart: Int = -1
}

Single label table view cell

class PartsCell: UITableViewCell {
    static let identifier: String = "partsCell"
    
    let theLabel = UILabel()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        theLabel.backgroundColor = .yellow
        theLabel.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(theLabel)
        let g = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            theLabel.topAnchor.constraint(equalTo: g.topAnchor),
            theLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            theLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            theLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor),
        ])
    }
}

Collection View cell with embedded table view

class EmbedTableCollectionCell: UICollectionViewCell, UITableViewDataSource, UITableViewDelegate {
    static let identifier: String = "embedCell"

    var callBack: ((EmbedTableCollectionCell, IndexPath) -> ())?
    
    var theData: BodyStruct = BodyStruct()
    
    let titleLabel = UILabel()
    let tableView = UITableView()

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    func commonInit() -> Void {
        
        titleLabel.font = .systemFont(ofSize: 20, weight: .bold)
        titleLabel.textAlignment = .center
        titleLabel.textColor = .blue
        
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(titleLabel)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(tableView)
        
        let g = contentView.layoutMarginsGuide
        
        let c1 = titleLabel.widthAnchor.constraint(equalToConstant: 260.0)
        let c2 = tableView.heightAnchor.constraint(equalToConstant: 132.0)
        c1.priority = .required - 1
        c2.priority = .required - 1
        
        NSLayoutConstraint.activate([
            
            titleLabel.topAnchor.constraint(equalTo: g.topAnchor),
            titleLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            titleLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            c1,
            
            tableView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8.0),
            tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
            c2,
            
        ])
        
        tableView.register(PartsCell.self, forCellReuseIdentifier: PartsCell.identifier)
        tableView.dataSource = self
        tableView.delegate = self
        
        contentView.backgroundColor = .systemYellow
    }
    
    func fillData(_ st: BodyStruct) {
        self.theData = st
        tableView.reloadData()
        titleLabel.text = theData.title
        if theData.selectedPart > -1 {
            tableView.selectRow(at: IndexPath(row: theData.selectedPart, section: 0), animated: false, scrollPosition: .none)
        } else {
            if let pth = tableView.indexPathForSelectedRow {
                tableView.deselectRow(at: pth, animated: false)
            }
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return theData.parts.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let c = tableView.dequeueReusableCell(withIdentifier: PartsCell.identifier, for: indexPath) as! PartsCell
        c.theLabel.text = theData.parts[indexPath.row]
        return c
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // tell the controller a part was selected
        callBack?(self, indexPath)
    }
}

Example view controller

class EmbedTestVC: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
    
    var myData: [BodyStruct] = []
    
    var collectionView: UICollectionView!
    let infoLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationController?.setNavigationBarHidden(true, animated: false)
        let fl = UICollectionViewFlowLayout()
        fl.scrollDirection = .vertical
        fl.estimatedItemSize = CGSize(width: 100, height: 100)
        fl.minimumLineSpacing = 12
        fl.minimumInteritemSpacing = 12
        collectionView = UICollectionView(frame: .zero, collectionViewLayout: fl)

        infoLabel.backgroundColor = .cyan
        infoLabel.text = "Selected:"
        
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(collectionView)
        infoLabel.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(infoLabel)

        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            
            infoLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
            infoLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            infoLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),

            collectionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            collectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            collectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            collectionView.bottomAnchor.constraint(equalTo: infoLabel.topAnchor, constant: -20.0),
            
        ])

        collectionView.register(EmbedTableCollectionCell.self, forCellWithReuseIdentifier: EmbedTableCollectionCell.identifier)
        collectionView.dataSource = self
        collectionView.delegate = self
        
        collectionView.backgroundColor = .systemBlue
        
        // some sample data
        let titles: [String] = [
            "head", "hair", "eyes", "ears", "nose", "arms", "hands",
        ]
        let parts: [[String]] = [
            ["round", "pointy", "square",],
            ["long", "short", "bald",],
            ["red", "green", "blue", "yellow", "cyan", "magenta",],
            ["Vulkan", "dangling", "attached",],
            ["pixie", "bulbous", "crooked",],
            ["tattooed", "hairy",],
            ["gloves", "mittens", "bare",],
        ]
        for (t, a) in zip(titles, parts) {
            let d = BodyStruct(title: t, parts: a)
            myData.append(d)
        }

    }
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        if let fl = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            fl.estimatedItemSize = CGSize(width: collectionView.frame.width, height: 100.0)
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return myData.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let c = collectionView.dequeueReusableCell(withReuseIdentifier: EmbedTableCollectionCell.identifier, for: indexPath) as! EmbedTableCollectionCell
        
        c.fillData(myData[indexPath.item])
        
        c.callBack = { [weak self] cell, partsPath in
            guard let self = self,
                  let idx = collectionView.indexPath(for: cell)
            else { return }

            // update data
            self.myData[idx.item].selectedPart = partsPath.row
            
            self.infoLabel.text = "Selected: \(self.myData[idx.item].title) / \(self.myData[idx.item].parts[partsPath.row])"
        }

        return c
    }
    
}

When you run that it will look like this - and when you select a "part" the label at the bottom will be updated with the selection. The data .selectedPart property will also be updated, so each table view will maintain its previously selected row:

enter image description here enter image description here

CodePudding user response:

In your cell classes you can have a delegate, and you can send your pass your data using that delegate methods.

Basic Diagram

Hope it helps.

  • Related