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
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:
CodePudding user response:
In your cell classes you can have a delegate, and you can send your pass your data using that delegate methods.
Hope it helps.