Reputation: 3961
I have two ViewController
: MainVC
, and ChildVC
. The MainVC
includes a CollectionView
with 5 cell
. Each of these cell
segues to the ChildVC
. On this ChildVC
, you can select different items which increases (or decreases) a counter on the ChildVC
(the counter just reads "## selected".)
Basically, I just want this counter data on the ChildVC
to be passed back onto a label of the respective MainVC
cell that was tapped. For example: If user taps the second cell on the MainVC
, selects 13 items on the ChildVC
, then returns back to the MainVC
, there will be a "13" in a label on the second cell. Then if the user taps the first cell, selects 5 items on the ChildVC
, then returns back to the MainVC
, there will be a "5" in a label on the first cell along with the "13" on second cell.
My progress:
I have decided that delegation is an appropriate solution for my requirements, as delegation makes it easy to pass data to/from VC's. I need assistance in passing data BACK from a ChildVC
TO a CollectionView
Cell.
My questions:
protocol
? (I wasn't sure if the indexPath
should be passed, so that the data displays on the correct cell on the MainVC
?)MainVC
, should the data received from the protocol
ChildVC
be sent to the CollectionViewCell
? or the MainVC
cellForItemAt
method?Update:
I have some progress below. But it's not working as intended.
In the below code, I have created both the ViewController (MainVC) and ChildVC. In the Child VC, there is a UISlider to emulate the selected counter. I would like this counter data passed back to the respective MainVC CollectionView Cells. What's happening now is the MainVC CollectionView gets a new cell added once I change the value of the slider! The 'Clear All Animals' btn needs to "zero out" the slider data for all the cells, but I haven't gotten that far yet..
View Controller (MainVC in my question above)
class ViewController: UIViewController {
var allAnimals = AnimalData.getAllAnimals()
@IBOutlet weak var mainCV: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
mainCV.dataSource = self
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "AnimalSegue" {
let childVC = segue.destination as! ChildVC
childVC.delegate = self
if let indexPath = self.mainCV.indexPath(for: sender as! AnimalCollectionViewCell) {
let animalData = self.allAnimals[indexPath.item]
childVC.animal = animalData
childVC.indexPath = indexPath
}
childVC.allIndexPaths = getAllIndexPaths()
}
}
func getAllIndexPaths() -> [IndexPath] {
var indexPaths: [IndexPath] = []
for i in 0..<mainCV.numberOfSections {
for j in 0..<mainCV.numberOfItems(inSection: i) {
indexPaths.append(IndexPath(item: j, section: i))
}
}
return indexPaths
}
}
extension ViewController: DataDelegate {
func zeroOut(for animalObject: AnimalModel, at indexPath: [IndexPath]) {
print("ZERO OUT")
self.mainCV.reloadData()
}
func updatedData(for animalObject: AnimalModel, at indexPath: IndexPath ) {
self.allAnimals[indexPath.item] = animalObject
self.mainCV.reloadItems(at: [indexPath])
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return allAnimals.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AnimalCell", for: indexPath as IndexPath) as! AnimalCollectionViewCell
let animal = allAnimals[indexPath.item]
cell.animal = animal
return cell
}
}
ChildVC
class ChildVC: UIViewController {
@IBOutlet weak var animalTitleLabel: UILabel!
@IBOutlet weak var labelCounter: UILabel!
@IBOutlet weak var sliderLabel: UISlider!
var delegate: DataDelegate?
var animal: AnimalModel?
var indexPath: IndexPath?
var allIndexPaths: [IndexPath]?
override func viewDidLoad() {
super.viewDidLoad()
animalTitleLabel.text = animal?.name
animalTitleLabel.textColor = animal?.color ?? .white
sliderLabel.value = Float(animal?.amountCounter ?? 0)
self.labelCounter.text = "\(Int(sliderLabel.value))"
}
@IBAction func closeButtonPressed(_ sender: UIButton) {
if let delegate = self.delegate,
let indexPath = self.indexPath,
let animal = self.animal {
delegate.updatedData(for: animal, at: indexPath)
}
self.dismiss(animated: true, completion: nil)
}
@IBAction func sliderChanged(_ sender: UISlider) {
let newValue = Int(sender.value)
labelCounter.text = "\(newValue)"
self.animal?.amountCounter = newValue
}
@IBAction func clearAllBtnPressed(_ sender: UIButton) {
if let delegate = self.delegate,
let all = self.allIndexPaths,
var animal = self.animal {
animal.amountCounter = 0
delegate.zeroOut(for: animal, at: all)
}
self.dismiss(animated: true, completion: nil)
}
}
Animal Collection View Cell
class AnimalCollectionViewCell: UICollectionViewCell {
@IBOutlet weak var animalLabel: UILabel!
@IBOutlet weak var counterLabel: UILabel!
var animal: AnimalModel! {
didSet {
self.updateUI()
}
}
func updateUI() {
animalLabel.text = animal.name
counterLabel.text = "\(animal.amountCounter)"
self.backgroundColor = animal.color
}
}
Data
struct AnimalData {
static func getAllAnimals() -> [AnimalModel] {
return [
AnimalModel(name: "Cats", amountCounter: 0, color: UIColor.red),
AnimalModel(name: "Dogs", amountCounter: 0, color: UIColor.blue),
AnimalModel(name: "Fish", amountCounter: 0, color: UIColor.green),
AnimalModel(name: "Goats", amountCounter: 0, color: UIColor.yellow),
AnimalModel(name: "Lizards", amountCounter: 0, color: UIColor.cyan),
AnimalModel(name: "Birds", amountCounter: 0, color: UIColor.purple)
]
}
}
Delegate
protocol DataDelegate {
func updatedData(for animalObject: AnimalModel, at: IndexPath)
func zeroOut(for animalObject: AnimalModel, at: [IndexPath])
}
Screenshots below of what is happening. See how Dogs is being added as another cell with the value of 23? What should happen is the 0 should change to a 23 on the second blue Dogs cell. I don't understand updating the data source and reloading the correct cells??
How do i simply pass back the slider data into the cell that was originally tapped?
Any help is appreciated
Upvotes: 1
Views: 1198
Reputation: 114826
You have the right idea with your delegation, but you need to be able to provide context back to your delegate; ie. what animal was being updated? To do this, either MainVC
needs to keep a property of the item that is being updated, or this information needs to be provided to the ChildVC
so that it can provide the information back to the MainVC
. I will use the latter approach.
Protocol
protocol DataDelegate {
func updatedData(for animalObject: AnimalModel, at: IndexPath)
func clearAll()
}
MainVC
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "AnimalSegue" {
let childVC = segue.destination as! ChildVC
childVC.delegate = self
if let indexPath = self.mainCV.indexPath(for: sender as! AnimalCollectionViewCell) {
let animalData = self.allAnimals[indexPath.item]
childVC.animal = animalData
childVC.indexPath = indexPath
}
}
}
extension ViewController: DataDelegate {
func updatedData(for animalObject: AnimalModel, at indexPath: IndexPath ) {
self.allAnimals[indexPath.item] = animalObject
self.mainCV.reloadItems(at: [indexPath])
}
func clearAll() {
for index in 0..<self.allAnimals.count {
self.allAnimals[index].count =0
}
self.mainCV.reloadData()
}
ChildVC
class ChildVC: UIViewController {
@IBOutlet weak var animalTitleLabel: UILabel!
@IBOutlet weak var labelCounter: UILabel!
@IBOutlet weak var sliderLabel: UISlider!
var delegate: DataDelegate?
var animal: AnimalModel?
var indexPath: IndexPath?
override func viewDidLoad() {
super.viewDidLoad()
animalTitleLabel.text = animal?.name
animalTitleLabel.textColor = animal?.color ?? .white
sliderLabel.value = animal?.count ?? 0
self.labelCounter.text = "\(Int(sliderLabel.value))"
}
@IBAction func closeButtonPressed(_ sender: UIButton) {
if let delegate = self.delegate,
let indexPath = self.indexPath,
let animal = self.animal {
delegate.updatedData(for: animal, at: indexPath)
}
self.dismiss(animated: true, completion: nil)
}
@IBAction func sliderChanged(_ sender: UISlider) {
let newValue = Int(sender.value)
labelCounter.text = "\(newValue)"
self.animal.count = newValue
}
@IBAction func clearAllBtnPressed(_ sender: UIButton) {
delegate.clearAll()
}
}
Updated
I have updated my answer to show how you could implement the clear all. In this case there is no reason to have the ChildVC
update the data model; it simply needs to invoke a delegate method to let the MainVC
know that it should update the model and refresh the collection view.
I think that this gives a hint as to why the ChildVC
is the wrong place for the "clear all" button; if the code feels a bit clunky then the user experience may be a bit clunky too. The clear all button should just be on your MainVC
- it doesn't make sense for a button on a animal-specific view to be affecting other animals. It also isn't "discoverable"; I don't find out about the clear all until I select an animal. I realise that this is just a "learning app" but user experience is an important part of iOS app development and so it is never too early to consider it; it can also impact the way you design your code as you can see here.
Upvotes: 2
Reputation: 1915
So this is a very incomplete display, but i believe this solution is something that you are lookin for
class mainVC {
var counters: [Int] = [0,0,0,0,0]
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(forIndexPath: indexPath) as CustomCell
cell.counterLabel = counters[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let childVC = ChildVC()
childVC.finishedSelecting = { counter in
self.counters.insert(counter, at: indexPath.item)
childVC.dismiss()
self.theCollectionView.reloadItems(at: [indexPath])
//Or
self.theCollectionView.reloadData()
}
present(childVC, animated: true)
}
}
class childVC {
var finishedSelecting: ((Int) -> ())?
var counter = 5
@objc func finishedButtonPressed() {
finishedSelecting?(counter)
}
func count() {
counter+=1
}
}
Upvotes: 0