Passing data from a child VC back to a Collection View Cell - ios

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:
Along with the selected counter count (Int), what other information should be passed to and from within the protocol? (I wasn't sure if the indexPath should be passed, so that the data displays on the correct cell on the MainVC?)
On the 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

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.

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

Related

how in the numberOfItemsInSection method to return the number equal to the value that I set in the slider from another view controller in Swift?

I am still learning swift, and I am trying to create a UICollectionView that would return the number of items that I set in the initial view controller using the slider, but my code doesn't work, how would I do this? Here is my code below:
class ViewController: UIViewController {
//MARK: - Outlets
#IBOutlet weak var firstLabel: UILabel! {
didSet {
firstLabel.text = "0"
}
}
#IBOutlet weak var firstSlider: UISlider! {
didSet {
firstSlider.value = 0
firstSlider.minimumValue = 0
firstSlider.maximumValue = 500
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func firstSliderAction(_ sender: UISlider) {
let firstSliderAction = Int(round(sender.value))
firstLabel.text = "\(firstSliderAction)"
}
}
// CollectionViewController
private let reuseIdentifier = "cell"
class CollectionViewController: UICollectionViewController {
var vc: ViewController!
override func viewDidLoad() {
super.viewDidLoad()
vc = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier: "ViewController") as? ViewController
}
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Int(vc.firstSlider.value)
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! CollectionCell
cell.backgroundColor = .green
return cell
}
}
First let me tell you why your code is not working as expected.
vc = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier: "ViewController") as? ViewController
Every time when we initiate view controller from corresponding storyboard it will create new object of that view controller, so based on that vc object will not have those value which has been set by earlier user interaction, in-short for that object didSet haven't called yet.
Now how you can achieve the above,
Well first we need to observe how you are navigating from ViewController to CollectionViewController based on code it looks like you are using segue so you can do below stuff.
class ViewController: UIViewController {
#IBOutlet weak var firstLabel: UILabel!
#IBOutlet weak var firstSlider: UISlider!
var sliderValue:Int = 0 {
didSet {
firstLabel.text = "\(sliderValue)"
}
}
override func viewDidLoad() {
super.viewDidLoad()
setUpInitialValues()
}
func setUpInitialValues() {
firstSlider.value = 0
firstSlider.minimumValue = 0
firstSlider.maximumValue = 500
}
// Value changed action
#IBAction func firstSliderAction(_ sender: UISlider) {
self.sliderValue = Int(round(sender.value))
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "navigateCollection" {
if let destinationVC = segue.destination as? CollectionViewController {
destinationVC.sliderValue = self.sliderValue
}
}
}
}
private let reuseIdentifier = "Cell"
class CollectionViewController: UICollectionViewController {
var sliderValue:Int = 0
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Register cell classes
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
self.collectionView.reloadData()
// Do any additional setup after loading the view.
}
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return sliderValue
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
// Configure the cell
cell.backgroundColor = .yellow
return cell
}
}

how to execute collectionView class from different ViewController

i have a collectionView class
class LC: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout{
//in LC
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let VC = segue.destination as? VC2 {
VC.Rpe = Pass
}
}
it's working fine , in VC2 i have a function when executed it should segue to the next cell in the collection view
i'm not sure how or what is the best way to do it (reload VC2 with the next collection view cell details?, or run the collection view functions programmatically )
update
import Foundation
import UIKit
class View2: UIViewController {
#IBOutlet var Q_Pic: UIImageView!
#IBOutlet var Q_que: UILabel!
var SelectedCell: Ques!
override func viewDidLoad() {
super.viewDidLoad()
Q_Pic.image = UIImage(named: SelectedCell.LIMG)
Q_que.text = SelectedCell.Q
}
#IBAction func herewego(_ sender: Any) {
print("when the user press this button it should take him directly to the next cell detail , i don't want the user to go back to collection view and choose the next cell")
}
}
data
let Q_A_TEST_MOH = [
Ques(Q: "Q1? ",LIMG: "1"),
Ques(Q: "Q2? ",LIMG: "2"),
Ques(Q: "Q3?",LIMG: "3"),
Ques(Q: "Q4?",LIMG: "4"),
Ques(Q: "Q5?",LIMG: "5")
]
struct Ques {
var Q : String
var LIMG: String
}
UICollectionViewController
import Foundation
import UIKit
class test:UIViewController,UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
#IBOutlet var CollectionView: UICollectionView!
var Levelssss: [Ques]!
var ToPass: Ques!
var SelectedCategory: String!
var Level: Int!
override func viewDidLoad() {
super.viewDidLoad()
CollectionView.delegate = self
CollectionView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Q_A_TEST_MOH.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "LevelCell2", for: indexPath) as? cell1 {
let r = Q_A_TEST_MOH[indexPath.item]
cell.congigureCell(EditLater: r)
return cell
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
ToPass = Q_A_TEST_MOH[indexPath.item]
performSegue(withIdentifier: "To", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let detalsVC = segue.destination as? View2 {
detalsVC.SelectedCell = ToPass
}
}
}
UICollectionViewCell
import UIKit
class cell1: UICollectionViewCell {
#IBOutlet var BB: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
BB.layer.cornerRadius = 10
}
func congigureCell(EditLater: Ques){
BB.setImage(UIImage(named: EditLater.LIMG), for: .normal)
}
}
Download project from here : download the project
Here is the fixed variant: https://www.dropbox.com/s/bc7ktktrbqg9x7t/test%202.zip?dl=0
Logic is simple: pass a whole array of data and index of selected object.
In VC2 on button click you just increment index and update content of your views.
Now you should just check is index not greater then count of element in array.

How to make data transition between table view cells with where button out of table view?

I have two custom table views. I need to pass first and second cell datas of DestinationTableView to first cell of MyCartTableView. How can I make transition between this two table view cells with outside of tableView.
I did tableView.indexPathForSelectedRow but this time I need to make with UIButtonoutside of tableView.
Below triggered with tableView cell.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "cellForFoodSegue" {
if let destinationViewController = segue.destination as? DetailViewController
{
let indexPath = self.mainTableView.indexPathForSelectedRow!
var foodNameArray: String
var foodPriceArray: Double
foodNameArray = foodNames[indexPath.row]
foodPriceArray = foodPrices[indexPath.row].purchaseAmount
destinationViewController.detailFoodName = foodNameArray
destinationViewController.detailFoodPrice = foodPriceArray
}
}
}
I tried below code but I did not success passing data with button.
#IBAction func addBasket(_ sender: Any) {
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "addToCartSegue") {
if let addToCartVC = segue.destination as? MyCartViewController {
let selectedCell = sender as! UITableViewCell
let indexPath = self.detailTableView.indexPath(for: selectedCell)
var foodNameArray: String
var foodPriceArray: Double
foodNameArray = foodNames[indexPath.row]
foodPriceArray = prices[indexPath.row].purchaseAmount
addToCartVC.fromDetailFoodName = foodNameArray
addToCartVC.fromDetailFoodPrice = prices[(indexPath?.row)!].purchaseAmount
}
}
}
Belows my MyViewController codes. Which is my added objects when tapped to addBasket button
class MyCartViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var fromDetailFoodName: [String?] = []
var fromDetailFoodPrice = Double()
var nameLabel = MyCartTableViewCell()
#IBOutlet weak var myCartTableView: UITableView!
#IBOutlet weak var totalPriceLabel: UILabel!
let foodNames = [
"Hamburger big mac",
"Cemal",
"Emre",
"Memo"
]
//TODO-: Delete my cart
#IBAction func deleteMyCart(_ sender: Any) {
}
//TODO: - Approve my cart
#IBAction func approveCart(_ sender: Any) {
}
override func viewDidLoad() {
super.viewDidLoad()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return section == 0 ? 1 : foodNames.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCartCell", for: indexPath) as! MyCartTableViewCell
cell.myCartFoodNameLabel?.text = fromDetailFoodName.description
cell.myCartFoodPriceLabel?.text = "\(fromDetailFoodPrice)₺"
return cell
}
}
You should get the index path of the data you want to pass in func addBasket(_ sender: Any).
For example, you can save index path as a property that referenced in class.
class StartViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var selectedIndexPath: IndexPath?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedIndexPath = indexPath
}
#IBAction func addBasket(_ sender: Any) {
if let indexPath = selectedIndexPath {
let destinationVC = MyCartViewController()
destinationVC.detailFoodName = foodNames[indexPath.row]
destinationVC.detailFoodPrice = foodPrices[indexPath.row].purchaseAmount
}
}
}
In MyCartViewController which is destination VC.
class MyCartViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var fromDetailFoodNames: [String?] = []
var fromDetailFoodPrices: [Double?] = []
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 1 && indexPath.last! <= fromDetailFoodPrices.indices.last! {
let cell = myCartTableView.dequeueReusableCell(withIdentifier: "myCartCell", for: indexPath) as! MyCartTableViewCell
let name = fromDetailFoodNames[indexPath.row]?.description ?? ""
let price = fromDetailFoodPrices[indexPath.row]
cell.myCartFoodNameLabel?.text = name
cell.myCartFoodPriceLabel?.text = "\(price)₺"
return cell
}
}
}
BTW, for better coding, you can implement OOP concept in your code. detailFoodName and detailFoodPrice should be in ONE object. Besides, var foodNameArray: String naming could be confusing. Rename it as var foodName: String would be better.

Call function in secondFile.swift/viewController from firstFile.swift/viewController?

I'm trying to call a function() made in my first ViewController from another function() made in the second ViewController.
It's a function to update the title of a button in the firstViewController.
I have searched but I can't find a way.
First ViewController // ViewController.swift
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
weightLabel.delegate = self
}
#IBAction func excerciseChooserButton(_ sender: UIButton) {
}
var weight = 0 {
didSet {
weightLabel.text = "\(weight)"
}
}
// User input WEIGHT
#IBOutlet weak var weightLabel: UITextField!
func textField(_ weightLabel: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let isNumber = CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: string))
let withDecimal = (
string == NumberFormatter().decimalSeparator &&
weightLabel.text?.contains(string) == false
)
return isNumber || withDecimal
}
#IBAction func plusWeight(_ sender: UIButton) {
weight += 5
}
#IBAction func minusWeight(_ sender: UIButton) {
weight -= 5
}
// User input REPS
#IBOutlet weak var repLabel: UILabel!
#IBAction func repSlider(_ sender: UISlider) {
let currentRepValue = Int(sender.value)
repLabel.text = "\(currentRepValue)"
let cm = Calculator(weight: weightLabel.text!, reps: repLabel.text!)
let result = cm.calcRM()
repMax.text = "1RM: \(result)kg"
}
#IBOutlet weak var repMax: UILabel!
#IBOutlet weak var excerciseLabel: UIButton!
func changeText() {
excerciseLabel.setTitle(Excercises.excChosen, for: .normal)
print(excerciseLabel)
}
#IBAction func unwindToViewController(segue:UIStoryboardSegue) {
}
}
// // // //
Second ViewController // ExcerciseChooserViewController.swift
import UIKit
struct Excercises {
static var excChosen:String? = ""
}
class ExcerciseChooserViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
// Data model: These strings will be the data for the table view cells
let excercises: [String] = ["Bench Press", "Squat", "Push Press", "Deadlift"]
// cell reuse id (cells that scroll out of view can be reused)
let cellReuseIdentifier = "cell"
// don't forget to hook this up from the storyboard
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Register the table view cell class and its reuse id
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
// (optional) include this line if you want to remove the extra empty cell divider lines
// self.tableView.tableFooterView = UIView()
// This view controller itself will provide the delegate methods and row data for the table view.
tableView.delegate = self
tableView.dataSource = self
}
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.excercises.count
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// create a new cell if needed or reuse an old one
let cell:UITableViewCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as UITableViewCell!
// set the text from the data model
cell.textLabel?.text = self.excercises[indexPath.row]
return cell
}
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let excerciseChosen = "\(excercises[indexPath.row])"
print("You tapped cell number \(indexPath.row).")
print(excerciseChosen)
goBackToOneButtonTapped((Any).self)
Excercises.excChosen = excerciseChosen
print(Excercises.excChosen!)
// call function to update text
ViewController.changeText()
}
#IBAction func goBackToOneButtonTapped(_ sender: Any) {
performSegue(withIdentifier: "unwindToViewController", sender: self)
}
}
Call it from unwindToViewController instead, no need to call it while the first view controller is not visible
There are many ways to do this, but I'll describe a simple one here.
Because you're going back to 'ViewController' via a segue, a good option for you is to override prepare(for:sender:). This will give you a reference to the destination view controller of that segue, which will then allow you to call functions or set properties in that view controller. You can read more about this method here.
Here are some basic steps:
In ViewController, update your changeText() method to accept a string parameter: changeText(_ text: String?).
Add a property to ExcerciseChooserViewController to hold the text you want to use: private var chosenExercise: String?
In your tableView:DidSelectRowAtIndexPath: method, set your new chosenExercise property to the string you want to pass to ViewController.
In prepare(for:sender:) of ExcerciseChooserViewController, grab a reference to destination view controller, downcast it to your subclass ViewController, and call your new method passing in the exerciseText string.
For example:
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var excerciseLabel: UIButton!
func changeText(_ text: String?) {
guard let text = text else { return }
excerciseLabel.setTitle(text, for: .normal)
print(excerciseLabel)
}
}
And in ExcerciseChooserViewController:
class ExcerciseChooserViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
private var chosenExercise: String?
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let excerciseChosen = "\(excercises[indexPath.row])"
print("You tapped cell number \(indexPath.row).")
print(excerciseChosen)
goBackToOneButtonTapped((Any).self)
Excercises.excChosen = excerciseChosen
print(Excercises.excChosen!)
chosenExercise = excerciseChosen
}
#IBAction func goBackToOneButtonTapped(_ sender: Any) {
performSegue(withIdentifier: "unwindToViewController", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationVC = segue.destination as? ViewController {
destinationVC.changeText(chosenExercise)
}
}
}

TableView Only Updates after Screen Rotates

Image of the tableview
I have a tableview with a collection view in each cell, all linked to an array. Each collection view has tags, so when I have stuff in the array from the beginning, all tableview cells and collection view cells appear properly in the app. But when I add an element to the array in the app itself (I have a second view controller with the stuff to do that), it works but the new table view cell only appears after the screen rotates (really odd). I have tried adding an object of the view controller with the table view in the second view controller where I add an element to the array. Then in the second view controller in ViewWillDisappear, I reloadData() through that object like this:
var vc : ViewController? = ViewController()
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
vc?.listOfActs.reloadData()
}
But this results in an EXC_BAD_INSTRUCTION
Then I tried adding self.listOfActs.reloadData() in the prepareForSegue in the view controller with the table view just so that I could see that it at least refreshes the data at some point in time but even that doesn't work when I click on add scene a second time.
UPDATE: New MainViewController
This is the new first view controller with the table view. I renamed it and have implemented the method for adding to array and reloading. It kind of works if I use an if let on the reloadData but then I'm back to square one where it only updates when I rotate the screen. When I get rid of the if let so it can actually try to update the table view, it gives me a Fata error: unexpectedly found a nil while unwrapping.
class MainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
//The Table View
#IBOutlet var AddActButton: UIButton!
#IBOutlet weak var listOfActs: UITableView!
var sectionTapped : Int?
var indexitemTapped : Int?
override func viewDidLoad() {
super.viewDidLoad()
listOfActs.delegate = self
listOfActs.dataSource = self
}
//Table View Functions
func numberOfSections(in tableView: UITableView) -> Int {
return actsCollection.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "actCell", for: indexPath) as! ActCell
cell.setCollectionViewDataSourceDelegate(self, forSection: indexPath.section)
return cell
}
//Add To Table View
func addObjects(appendage: Act) {
actsCollection.append(appendage)
if let shit = listOfActs {
shit.reloadData()
}
}
//Header Cell
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cellHeader = tableView.dequeueReusableCell(withIdentifier: "headerCell") as! HeaderCell
cellHeader.headerName.text = actsCollection[section].actName
return cellHeader
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 40
}
}
//Scene Collection in Act Cell
extension MainViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return actsCollection[collectionView.tag].actScenes.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "sceneCell", for: indexPath) as! SceneCell
cell.sceneTitle.text = actsCollection[collectionView.tag].actScenes[indexPath.item].sceneTitle
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
sectionTapped = collectionView.tag
indexitemTapped = indexPath.item
performSegue(withIdentifier: "showDetail", sender: self)
}
//Segue Prepare
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
let detailsVC = segue.destination as! SceneDetailController
detailsVC.textToAppearInSceneName = actsCollection[sectionTapped!].actScenes[indexitemTapped!].sceneTitle
}
}
}
UPDATE:New second view controller, the one that adds to the array.
class AddActController: UIViewController, UITextFieldDelegate {
#IBOutlet var sceneLiveName: UILabel!
#IBOutlet var sceneNameTextField: UITextField!
#IBOutlet var sceneDescriptionTextField: UITextField!
#IBOutlet var AddSceneButton: UIButton!
#IBOutlet var cardBounds: UIView!
var newName: String? = ""
#IBOutlet var cardShadow: UIView!
var shit = MainViewController()
override func viewDidLoad() {
super.viewDidLoad()
sceneNameTextField.delegate = self
AddSceneButton.alpha = 0.0
cardBounds.layer.cornerRadius = 20.0
cardShadow.layer.shadowRadius = 25.0
cardShadow.layer.shadowOpacity = 0.4
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 0.2){
self.AddSceneButton.alpha = 1.0
}
}
#IBAction func exitButton(_ sender: UIButton) {
dismiss(animated: true, completion: nil)
}
#IBAction func addSceneButton(_ sender: UIButton) {
if newName == "" {
sceneLiveName.text = "Enter Something"
sceneNameTextField.text = ""
}
else {
let appendAct: Act = Act(actName: newName!, actTheme: "Action", actScenes: [Scene(sceneTitle: "Add Act", sceneDescription: "")])
shit.addObjects(appendage: appendAct)
dismiss(animated: true, completion: nil)
}
}
//MARK: textField
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let text: NSString = (sceneNameTextField.text ?? "") as NSString
let resultString = text.replacingCharacters(in: range, with: string)
sceneLiveName.text = resultString
newName = String(describing: (sceneLiveName.text)!.trimmingCharacters(in: .whitespacesAndNewlines))
return true
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
sceneNameTextField.resignFirstResponder()
return true
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
Here is the class for the uitableviewcell that contains its own collection view.
class ActCell: UITableViewCell {
#IBOutlet fileprivate weak var sceneCollection: UICollectionView!
}
extension ActCell {
func setCollectionViewDataSourceDelegate<D: UICollectionViewDataSource & UICollectionViewDelegate>(_ dataSourceDelegate: D, forSection section: Int) {
sceneCollection.delegate = dataSourceDelegate
sceneCollection.dataSource = dataSourceDelegate
sceneCollection.tag = section
sceneCollection.reloadData()
}
}
And here is the model with the user's data including the acts and scenes.
struct Scene {
var sceneTitle: String
var sceneDescription: String
//var characters: [Character]
//var location: Location
}
struct Act {
var actName: String
var actTheme: String
var actScenes : [Scene] = []
}
var actsCollection : [Act] = [
Act(actName: "dfdsfdsfdsf", actTheme: "Action", actScenes: [Scene(sceneTitle: "Example Act", sceneDescription: "")])
]
Any help is greatly appreciated. Thank you to all.
So if I'm not mistaken I believe the viewDidLoad method gets call during screen rotations. So this explains why it update during so. Now to get it to update without rotating the device, I would add an observer in the notificationCenter to watch for any updates to the tableView then call a #selector to do the reloadData(). So here is an example of this. In the viewDidLoad method add
NotificationCenter.default.addObserver(self, selector: #selector(refreshTable), name: NSNotification.Name(rawValue: "load"), object: nil)
Then add the method refreshTable()
func refreshTable() {
listOfActs.reloadData()
}
This is basically how I handle keeping the tableView refreshed.
Well - viewDidLoad is loaded only for the first time controller loads his view (not sure about rotation).
If you really need - you can reload tableView in viewWillAppear - but I wouldn't do this.
Instead of
actsCollection.append(appendAct)
dismiss(animated: true, completion: nil)
create a method on the first controller like addObjectToList(appendAct) and in that method, just easily append object to your list array and reload tableView after adding.
You will be reloading tableView only when you really add something to your list and not every time controller appears, you also don't need notification observer.
EDIT - UPDATE
What is this?
if newName == "" {
sceneLiveName.text = "Enter Something"
sceneNameTextField.text = ""
}
else {
let appendAct: Act = Act(actName: newName!, actTheme: "Action", actScenes: [Scene(sceneTitle: "Add Act", sceneDescription: "")])
shit.addObjects(appendage: appendAct)
dismiss(animated: true, completion: nil)
}
I mean - what is shit.AddObjects? Shit is defined as tableView - but you have to call this method on instance of your controller.
Another thing - change your setup from sections == number of items with 1 row to be one section with number of rows == number of items. :)

Resources