skip to Main Content

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()
    }
}

2

Answers


  1. 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

    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search