Alen
Alen

Reputation: 3

iOS development Swift Custom Cells

I'm developing an app in Swift and I have problem with Custom Cells. I have one Custom Cell and when you click on Add button it creates another Custom Cell. Cell has 3 textfields, and 2 buttons. Those textfields are name, price, and amount of the ingredient of meal that I am creating. When I use only one Custom Cell or add one more to make it two, the data is stored properly. But when I add 2 Custom Cell (3 Custom Cells in total) I have problem of wrong price, only last two ingredients are calculated in price. When I add 3 Custom Cells (4 Custom Cells in total) it only recreates first cell with populated data like in first cell. On finish button tap, I get an fatal error: Found nil while unwrapping Optional value.

View Controller

import UIKit
import CoreData

class AddMealViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {

    @IBOutlet weak var mealNameTF: UITextField!

    @IBOutlet weak var addMealsCell: UITableViewCell!
    @IBOutlet weak var finishButton: UIButton!
    @IBOutlet weak var resetButton: UIButton!
    @IBOutlet weak var addButton: UIButton!
    @IBOutlet weak var addMealTableView: UITableView!

    @IBOutlet weak var productPrice: UILabel!
    let currency = "$" // this should be determined from settings
    var priceTotal = "0"

    override func viewDidLoad() {
        super.viewDidLoad()
        addMealTableView.delegate = self
        addMealTableView.dataSource = self
        borderToTextfield(textField: mealNameTF)
        mealNameTF.delegate = self
    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            self.view.endEditing(true)
            return true;
        }

    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return counter
    }

    var counter = 1

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return counter
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier:
            "addMealsCell", for: indexPath) as! AddMealsTableViewCell

        borderToTextfield(textField: (cell.amountTF)!)
        borderToTextfield(textField: (cell.ingredientNameTF)!)
        //borderToTextfield(textField: cell.normativeTF)
        borderToTextfield(textField: (cell.priceTF)!)



        return cell

    }


    @IBAction func addButton(_ sender: UIButton) {
        counter += 1

        addMealTableView.register(AddMealsTableViewCell.self, forCellReuseIdentifier: "addMealsCell")
        addMealTableView.reloadData()

    }



    @IBAction func resetButton(_ sender: UIButton) {
       mealNameTF.text = ""
        for c in 0..<counter{
            let indexPath = IndexPath(row: c, section:0)
            let cell = addMealTableView.cellForRow(at: indexPath) as! AddMealsTableViewCell
            cell.amountTF.text = ""
            cell.ingredientNameTF.text = ""
          //  cell.normativeTF.text = ""
            cell.priceTF.text = ""
        }
        productPrice.text = "\(currency)0.00"
        priceTotal = "0"
        counter = 1
    }

    @IBAction func finishButton(_ sender: UIButton) {
        for c in (0..<counter){
            if let cell = addMealTableView.cellForRow(at: IndexPath(row: c, section: 0)) as? AddMealsTableViewCell {
                cell.amountTF.delegate = self
                cell.ingredientNameTF.delegate = self
                // cell.normativeTF.delegate = self
                cell.priceTF.delegate = self

                guard cell.priceTF.text?.isEmpty == false && cell.amountTF.text?.isEmpty == false && mealNameTF.text?.isEmpty == false && cell.ingredientNameTF.text?.isEmpty == false
                    else {
                        return
                }

                if cell.priceTF.text?.isEmpty == false{
                //    if (true) {
                    let tfp = Double((cell.priceTF.text!))!*Double((cell.amountTF.text!))!
                    var ttp = Double(priceTotal)
                    ttp! += tfp
                    priceTotal = String(ttp!)
              //  }
                    }}
            }


        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        let context = appDelegate.persistentContainer.viewContext
        let newMeal = NSEntityDescription.insertNewObject(forEntityName: "Meal", into: context)
        let mealName = mealNameTF.text
        newMeal.setValue(mealName, forKey: "name")
        newMeal.setValue(priceTotal, forKey: "price")
        do {
            try context.save()

            print("Spremljeno")
        } catch  {
            print("Neki error")
        }

        productPrice.text = currency + priceTotal
    }

    @IBAction func addNewIngredientButton(_ sender: UIButton) {


    }
    func borderToTextfield(textField: UITextField){
        let border = CALayer()
        let width = CGFloat(2.0)
        border.borderColor = UIColor.white.cgColor
        border.frame = CGRect(x: 0, y: textField.frame.size.height - width, width:  textField.frame.size.width, height: textField.frame.size.height)

        border.borderWidth = width
        textField.layer.addSublayer(border)
        textField.layer.masksToBounds = true
        textField.tintColor = UIColor.white
        textField.textColor = UIColor.white
        textField.textAlignment = .center

    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        self.view.endEditing(true)
        return true;
    }
}

Cell

class AddMealsTableViewCell: UITableViewCell, UITextFieldDelegate{

    @IBOutlet weak var DropMenuButton: DropMenuButton!
    @IBOutlet weak var addNewIngredient: UIButton!
    @IBOutlet weak var ingredientNameTF: UITextField!

   // @IBOutlet weak var normativeTF: UITextField!

    @IBOutlet weak var amountTF: UITextField!

    @IBOutlet weak var priceTF: UITextField!

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        self.endEditing(true)
        return true;
    }

    override func prepareForReuse() {
        self.amountTF.text = ""
        self.priceTF.text = ""
        self.ingredientNameTF.text = ""
    }

    @IBAction func DropMenuButton(_ sender: DropMenuButton) {
        DropMenuButton.initMenu(["kg", "litre", "1/pcs"], actions: [({ () -> (Void) in
            print("kg")
            sender.titleLabel?.text = "kg"
        }), ({ () -> (Void) in
            print("litre")

            sender.titleLabel?.text = "litre"
        }), ({ () -> (Void) in
            print("1/pcs")
            sender.titleLabel?.text = "1/pcs"
        })])
    }

    @IBAction func addNewIngredient(_ sender: UIButton) {


        let name = ingredientNameTF.text
        let amount = amountTF.text
        let price = priceTF.text
        //  let normative = normativeTF.text

        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        let context = appDelegate.persistentContainer.viewContext
        let newIngredient = NSEntityDescription.insertNewObject(forEntityName: "Ingredient", into: context)

       newIngredient.setValue(name, forKey: "name")
       // newIngredient.setValue(normative, forKey: "normative")
       newIngredient.setValue(amount, forKey: "amount")
       newIngredient.setValue(price, forKey: "price")

        do {

            try context.save()
            print("Spremljeno")
        } catch  {
            print("Neki error")
        }
    }
}

Upvotes: 0

Views: 154

Answers (1)

ColdLogic
ColdLogic

Reputation: 7275

Your code is very difficult to read, but I suspect the problem may be here:

func numberOfSections(in tableView: UITableView) -> Int {
    return counter
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return counter
}

You're returning the same number of rows for both the sections and rows for sections. So if you have 1 ingredient you are saying there is 1 section with 1 row. But if you have 2 ingredients you are saying there are 2 sections, each with 2 cells (4 cells total).

There are many other things to fix with your code, here are a few:

The biggest thing is that you are making this very difficult with the counter variable you have. If instead you have an array of ingredients

var ingredients = [Ingredient]()

You can use that to setup everything for the count of your table. Something like this:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return ingredients.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier:
        "addMealsCell", for: indexPath) as! AddMealsTableViewCell

    let ingredient = ingredients[indexPath.row]

    cell.ingredientNameTF.text = ingredient.name
    cell.normativeTF.text = ingredient.normative
    cell.amountTF.text = ingredient.amount
    cell.priceTF.text = ingredient.price

    return cell
}

All of these can be set in interface builder (you dont need lines of code in your view did load for them):

    addMealTableView.delegate = self
    addMealTableView.dataSource = self
    mealNameTF.delegate = self

This line should be in your viewDidLoad function, you only need to register the class once, you're doing it everytime add is pressed.

addMealTableView.register(AddMealsTableViewCell.self, forCellReuseIdentifier: "addMealsCell")

Your action names should be actions

 @IBAction func addButtonPressed(_ sender: UIButton)
 @IBAction func resetButtonPressed(_ sender: UIButton)
 @IBAction func finishButtonPressed(_ sender: UIButton)

Thanks. Now when that ViewController loads I have no cells. My array is now empty so I have to implement some code to addButton which creates another cell and reloads tableView. How can I do that?

You just need to add a new ingredient object to the ingredients array and reload the data of the table view.

@IBAction func addButtonPressed(_ sender: UIButton) {
    let newIngredient = Ingredient()
    ingredients.append(newIngredient)
    tableView.reloadData()
}

Upvotes: 1

Related Questions