I am developing an app using Xcode in Swift. Briefly I have a tableViewController that houses many cells. There is a button in every cell (A stepper to be exact). My question is, how can I get the index of the cell which houses the stepper every time I click on it (the stepper)?
*Bonus point for knowing if the user pressed the 'Minus' or 'Plus' side of the stepper.
I search a very long time and I am still not sure how to approach this situation, nothing I try seems to be working.
Some context:
My tableViewController looks like this. enter image description here
Every cell has a few labels which are populated with informations taken from a file that I import. When importing the file I store all of its information in an array which I read every time I populate the tableView. Along with that, I have a label that will increment or decrement using the stepper. This allows me to track how much of which item I need to order. In the end, what I want to do is update my array with the amount I want to order so that it is saved for the next time I quit and reopen the app.
I have two ways that I think might work,
1: use the stepper to trigger a function that will increment or decrement the value for the order of its own cell using the index value for the cell in which the stepper is located.
2: Have the stepper only change the orderLabel, then have a save button that will go through every tableCell and read the orderLabel to save it into the array using the indexValue.
I don't know which would be the best way but I feel like the reading and the saving of the oderLabel to the array has to happen in my ItemTableViewController, as that is where I created the array which stores all my data.
Thanks, and I hope this will help.
In my humble opinion the most efficient way in Swift (and meanwhile even in Objective-C) is a callback closure / block.
It's very easy to implement. The benefit is:
In the UITableViewCell
subclass declare a callback
property with a closure (a Double
parameter, no return value) and add an IBAction
connected to the stepper. In the action call callback
passing the value of the stepper:
class MyTableViewCell: UITableViewCell {
var callback : ((Double)->())?
@IBAction func stepperChanged(_ sender: UIStepper) {
callback?(sender.value)
}
}
In the view controller in cellForRowAt
assign a closure to the callback
variable and handle the returned value. The model item as well as the index path is captured in the closure.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyIdentifier", for: indexPath) as! MyTableViewCell
let item = dataSourceArray[indexPath.row] // get the model item from the data source array
// update UI
cell.callback = { value in
print(indexPath, value)
// do something with the new value for example update the model
}
return cell
}
In Swift 4 there is a smarter alternative, the new NSKeyValueObservation
class. The benefit of this solution is that you can determine easily whether the stepper was incremented or decremented.
Instead of the IBAction
create an IBOutlet
, connect it to the stepper and instead of the callback
property declare a property of type NSKeyValueObservation
class MyTableViewCell: UITableViewCell {
@IBOutlet weak var stepper : UIStepper!
var observation : NSKeyValueObservation?
}
In cellForRowAt
assign the observer and pass the options old
and new
. In the closure compare oldValue
with newValue
to get the direction.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyIdentifier", for: indexPath) as! MyTableViewCell
let item = dataSourceArray[indexPath.row] // get the model item from the data source array
// update UI
cell.observation = cell.stepper.observe(\.value, options: [.old, .new]) { (stepper, change) in
let wasIncremented = change.oldValue! < change.newValue!
print("new value:", change.newValue!, "stepper was incremented:", wasIncremented)
}
return cell
}