0

So I currently have a tableView in a ViewController called GearComponentViewController that has custom tableViewCells that have a UIStepper in them to control a quantity label and conforms to the custom protocol Stepper.

protocol Stepper {
func stepperWasPressed(didIncrease: Bool, namePassed: String, userindexPath: Int)
}

And here's the code for the GearComponentTableViewCell:

class GearComponentTableViewCell: UITableViewCell {

var mainVC = GearComponentViewController()
var tableViewCellPosition: Int! = nil

// Image
@IBOutlet weak var itemImage: UIImageView!

// Name
@IBOutlet weak var itemName: UILabel!

// Weight
@IBOutlet weak var itemWeight1: UILabel!
@IBOutlet weak var itemWeight2: UILabel!

// Quanity
@IBOutlet weak var itemQuanity: UILabel!
@IBAction func stepperPressed (_ sender: UIStepper!){
    if (sender.value == 1) {
        print("up and item: \(itemName.text!)");
        sender.value = 0
        mainVC.stepperWasPressed(didIncrease: true, namePassed: itemName.text!, userindexPath: tableViewCellPosition)
    } else if (sender.value == -1) {
        print("down and item: \(itemName.text!)");
        sender.value = 0
        mainVC.stepperWasPressed(didIncrease: false, namePassed: itemName.text!, userindexPath: tableViewCellPosition)
    }
}

// Notes
@IBOutlet weak var itemNotes: UILabel!

}

It detects if the user clicked on the plus button or the minus button on the stepper and calls the function from the protocol stepperWasPressed()

Finally here's the code for the mainVC referenced:

extension GearComponentViewController: Stepper {
    
    func refreshTableViewCell() {
        print("arrayPosition: \(arrayPosition)")
//        gearTableView.reloadData()
    }
    
    
    
    func stepperWasPressed(didIncrease: Bool, namePassed: String, userindexPath: Int) {
        
        if didIncrease {
            arrayPosition = userindexPath
            print("arrayPosition: \(arrayPosition)")
//            itemArray[userindexPath].quanity += 1
            print("userindexPath: \(userindexPath) -- namePassed: \(namePassed) -- didIncrease: \(didIncrease)")
            print("increase selected")
            refreshTableViewCell()
        }else {
            arrayPosition = userindexPath
            print("arrayPosition: \(arrayPosition)")
//            itemArray[userindexPath].quanity -= 1
            print("userindexPath: \(userindexPath) -- namePassed: \(namePassed) -- didIncrease: \(didIncrease)")
            print("decrease selected")
            refreshTableViewCell()
        }
    }
}

When I try to do either itemArray[userindexPath].quanity += 1 or itemArray[userindexPath].quanity -= 1 I'm getting the error listed in the title, but to be even able to call that part of the function the count of my itemArray has to be greater than zero/empty because I'm being presented a cell with a stepper in it.

Full GearComponentViewController:

import UIKit

class GearComponentViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    var arrayPosition: Int = 0
    
    // Data Sources
    var itemArray: [GearItem] = []
    var totalWeight1: Int = 0
    var totalWeight2: Int = 0
    var totalItems: Int = 0
    
    // Weight Label Outlets
    @IBOutlet weak var weight1LabelOutlet: UILabel!
    @IBOutlet weak var weight2LabelOutlet: UILabel!
    
    // Item Count Label Outlet
    
    @IBOutlet weak var totalCountLabelOutlet: UILabel!
    
    // TableView Outlet
    @IBOutlet weak var gearTableView: UITableView!
    
    // Bar Button Item
    @IBAction func addItemPressed(_ sender: Any) {
        presentAlert()
    }
    
    // View Did Load
    override func viewDidLoad() {
        super.viewDidLoad()
        gearTableView.dataSource = self
        gearTableView.delegate = self
        print("Gear Component View Controller successfully loaded.")
        
        updateUI()
        // Do any additional setup after loading the view.
    }
    
    //MARK: - UI Updater
    func updateUI() {
        addTotalWeight()
        addAllItems()
    }
    
    // Adding Total Weight
    func addTotalWeight() {
        var addedWeight1: Int = 0
        var addedWeight2: Int = 0
        
        for item in itemArray {
            addedWeight1 += item.weight1 * item.quantity
            addedWeight2 += item.weight2 * item.quantity
        }
        
        totalWeight1 = addedWeight1
        totalWeight2 = addedWeight2
        
        weight1LabelOutlet.text = String("\(totalWeight1)")
        weight2LabelOutlet.text = String("\(totalWeight2)")
    }
    
    // Item Count
    func addAllItems() {
        var addedValue: Int = 0
        
        for item in itemArray {
            addedValue += item.quantity
        }
        totalItems = addedValue
        
        totalCountLabelOutlet.text = String("\(totalItems)")
        print("Total items: \(totalItems)")
    }
    
   
    
    
    //MARK: - Alert
    func presentAlert() {
        let alertController = UIAlertController(title: "Add Item", message: "Enter item info here", preferredStyle: .alert)
        
        // AlertController Textfields
        alertController.addTextField { (textField) in
            textField.placeholder = "Name"
        }
        alertController.addTextField { (textField) in
            textField.placeholder = "Weight 1"
            textField.keyboardType = .decimalPad
        }
        alertController.addTextField { (textField) in
            textField.placeholder = "Weight 2"
            textField.keyboardType = .decimalPad
        }
        alertController.addTextField { (textField) in
            textField.placeholder = "Quanity"
            textField.keyboardType = .decimalPad
        }
        alertController.addTextField { (textField) in
            textField.placeholder = "Notes"
        }
        
        // AlertController Actions
        let continueAction = UIAlertAction(title: "Add", style: .default) { [self, weak alertController] _ in
            guard let textFields = alertController?.textFields else { return }
            
            if let userItemName = textFields[0].text,
               let userItemWeight1 = textFields[1].text,
               let userItemWeight2 = textFields[2].text,
               let userItemQuanity = textFields[3].text,
               let userItemNotes = textFields[4].text {
                
                print("Name: \(userItemName)")
                print("Item Weight 1: \(userItemWeight1)")
                print("Item Weight 2: \(userItemWeight2)")
                print("Item Quantity: \(userItemQuanity)")
                print("Item Notes: \(userItemNotes)")
                
                let userSubmittedItem = GearItem(itemName: userItemName, itemImage: UIImage(systemName: "photo.on.rectangle.angled")!, itemWeight1: Int(userItemWeight1) ?? 0, itemWeight2: Int(userItemWeight2) ?? 0, itemQuantity: Int(userItemQuanity) ?? 1, itemNotes: userItemNotes ?? "", creationPosition: itemArray.count + 1)
                
                self.itemArray.append(userSubmittedItem)
                gearTableView.reloadData()
                updateUI()
            }
        }
        
        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
        cancelAction.setValue(UIColor.red, forKey: "titleTextColor")
        
        alertController.addAction(continueAction)
        alertController.addAction(cancelAction)
        
        self.present(alertController,
                     animated: true)
    }
    
    //MARK: - TableView Protocol Functions
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return itemArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = gearTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! GearComponentTableViewCell
        cell.itemName.text = itemArray[indexPath.row].name
        cell.itemImage.image = itemArray[indexPath.row].image
        cell.itemWeight1.text = String("\(itemArray[indexPath.row].weight1)")
        cell.itemWeight2.text = String("\(itemArray[indexPath.row].weight2)")
        cell.itemQuanity.text = String("\(itemArray[indexPath.row].quantity)")
        cell.itemNotes.text = itemArray[indexPath.row].notes
        
        cell.tableViewCellPosition = indexPath.row
        
        return cell
    }
    
    //MARK: - Swipeable TableViewCell
    private func deleteItem(_ indexPath: IndexPath) {
        itemArray.remove(at: indexPath.row)
        gearTableView.reloadData()
        updateUI()
        print("Item Deleted")
    }
    
    func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let action = UIContextualAction(style: .normal, title: "Delete") { [weak self] (action, view, completionHandler) in
            self?.deleteItem(indexPath)
            completionHandler(true)
        }
        action.backgroundColor = .systemRed
        
        return UISwipeActionsConfiguration(actions: [action])
        
    }
}

extension GearComponentViewController: Stepper {
    
    func refreshTableViewCell() {
        print("arrayPosition: \(arrayPosition)")
//        itemArray[arrayPosition].quantity += 1
//        gearTableView.reloadData()
    }
    
    
    
    func stepperWasPressed(didIncrease: Bool, namePassed: String, userindexPath: Int) {
        
        if didIncrease {
            arrayPosition = userindexPath
            print("arrayPosition: \(arrayPosition)")
//            itemArray[userindexPath].quanity += 1
            print("userindexPath: \(userindexPath) -- namePassed: \(namePassed) -- didIncrease: \(didIncrease)")
            print("increase selected")
            refreshTableViewCell()
        }else {
            arrayPosition = userindexPath
            print("arrayPosition: \(arrayPosition)")
//            itemArray[userindexPath].quanity -= 1
            print("userindexPath: \(userindexPath) -- namePassed: \(namePassed) -- didIncrease: \(didIncrease)")
            print("decrease selected")
            refreshTableViewCell()
        }
    }
}

And here's an image of what my app looks like right now for reference.

  • You probably don't want to _create_ a GearComponentViewController (`var mainVC`) in your GearComponentTableViewCell. What you really should have in your cell is a _closure_ variable which handles the users actions. This closure should be initialised in `tableView(_: cellForRowAt:)` and calls into the table view controller. Ensure you do not introduce reference cycles. – CouchDeveloper Aug 07 '22 at 08:20

1 Answers1

1

The problem is in your delegate function. You try to delegate on a new instance of your GearComponentViewController(). Try replacing the var mainVC = GearComponentViewController() in your custom cell Class with

var delegate:Stepper?

and call the delegate with

delegate?.stepperWasPressed(didIncrease:...

also don't forget to register your delegate on your GearComponentViewController() in the cellForRow function like

cell.delegate = self
snksnk
  • 1,566
  • 4
  • 21
  • 46
  • 2
    One thing I would add is that your cells should never keep track of their indexPath. What happens if you insert or delete a cell? All of the indexPath's tracked by every other cell becomes invalid. If you are going to use a delegate, pass the cell object. That way you can call `tableView.indexPath(for: cell)` to get the latest indexPath for a cell. Remember, indexPaths and cell objects don't remain paired. – Rob C Aug 07 '22 at 06:32