I'm a Swift beginner and I'm trying to make a simple app for ordering food. The user could add a new order by setting food name
, price
and serving
. After adding an order, that order will be shown on the tableView as a FoodTableViewCell
, and the user could change the serving with an UIStepper called stepper
in each cell. Each order is a FoodItem
stored in an array called foodList
, and you can see all orders listed in a tableView in ShoppingListVC
.
My problem is: When I press "+" or "-" button on stepper
, my servingLabel
doesn't change to corresponding value. I tried to use NotificationCenter
to pass serving value to stepper
, and store new value back to food.serving
after stepperValueChanged
with delegate pattern. However, there still seems to be some bugs. I've been kind of confused after browsing lots of solutions on the Internet. Any help is appreciated.
Update
I removed NotificationCenter
and addTarget
related methods as @Tarun Tyagi 's suggestion. Now my UIStepper value turns back to 1 whereas the servingLabels are showing different numbers of serving. Since NotificationCenter doesn't help, how can I connect the label and stepper value together? Is it recommended to implement another delegate?
Here are my codes(Updated on July 8):
FoodItem
class FoodItem: Equatable {
static func == (lhs: FoodItem, rhs: FoodItem) -> Bool {
return lhs === rhs
}
var name: String
var price: Int
var serving: Int
var foodID: String
init(name: String, price: Int, serving: Int) {
self.name = name
self.price = price
self.serving = serving
self.foodID = UUID().uuidString
}
}
ViewController
import UIKit
class ShoppingListVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
var foodList = [FoodItem]()
@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
...
for i in 1...5 {
let testItem = FoodItem(name: "Food\(i)", price: Int.random(in: 60...100), serving: Int.random(in: 1...10))
self.foodList.append(testItem)
}
}
// MARK: - Table view data source
...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "foodCell", for: indexPath) as! FoodTableViewCell
let food = foodList[indexPath.row]
cell.nameLabel.text = food.name
cell.priceLabel.text = "$\(String(food.price)) / serving"
cell.servingLabel.text = "\(String(food.serving)) serving"
cell.stepper.tag = indexPath.row
cell.delegate = self
return cell
}
}
// MARK: - FoodTableViewCellDelegate Method.
extension ShoppingListVC: FoodTableViewCellDelegate {
func stepper(_ stepper: UIStepper, at index: Int, didChangeValueTo newValue: Double) {
let indexPath = IndexPath(item: index, section: 0)
guard let cell = tableView.cellForRow(at: indexPath) as? FoodTableViewCell else { return }
let foodToBeUpdated = foodList[indexPath.row]
print("foodToBeUpdated.serving: \(foodToBeUpdated.serving)")
foodToBeUpdated.serving = Int(newValue)
print("Value changed in VC: \(newValue)")
cell.servingLabel.text = "\(String(format: "%.0f", newValue)) serving"
}
}
TableViewCell
import UIKit
protocol FoodTableViewCellDelegate: AnyObject {
func stepper(_ stepper: UIStepper, at index: Int, didChangeValueTo newValue: Double)
}
class FoodTableViewCell: UITableViewCell {
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var priceLabel: UILabel!
@IBOutlet weak var servingLabel: UILabel!
@IBOutlet weak var stepper: UIStepper!
weak var delegate: FoodTableViewCellDelegate?
@IBAction func stepperValueChanged(_ sender: UIStepper) {
sender.minimumValue = 1
servingLabel.text = "\(String(format: "%.0f", sender.value)) serving"
// Pass the new value to ShoppingListVC and notify which cell to update using tag.
print("sender.value: \(sender.value)")
delegate?.stepper(stepper, at: stepper.tag, didChangeValueTo: sender.value)
}
override func awakeFromNib() {
super.awakeFromNib()
print(stepper.value)
}
}
Initially FoodTableViewCell
is the ONLY target for UIStepper
value changed (looking at @IBAction
inside FoodTableViewCell
).
When you dequeue a cell to display on screen, you call -
cell.stepper.addTarget(self, action: #selector(stepperValueChanged(_:)), for: .valueChanged)
which causes your ShoppingListVC
instance to be added as an additional target every time a cellForRow
call is executed.
Things to fix :
NotificationCenter
related code from both classes.cell.stepper.addTarget()
line as well.This would give you a better idea of why it is happening this way. Update your question with these changes in case you still don't have what you want.
// Inside cellForRow
cell.stepper.value = food.serving