I have a UITableViewController with custom UITableViewCells which contain a UILabel and a UIButton.
The problem is that the UILabel which is initially set to 0, is not changing when the button is pressed which changes its particular value in the array. When I use println() the actual array has been changed, but the UILabel has not.
The following is the code from my UITableViewController:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let groupChatCell = tableView.dequeueReusableCellWithIdentifier("groupChatCell", forIndexPath: indexPath) as textCell
groupChatCell.voteScoreLabel.text="\(groupVote[indexPath.row])"
groupChatCell.voteScoreLabel.tag=indexPath.row
return groupChatCell
}
voteScoreLabel is the UILabel which I want to update from the array.
groupVote is the array that is meant to populate the label with the scores. I set the UILabel tag equal to its index path so that I can then reference it when I need to pick out a particular value of the array.
The following is from my custom UITableViewCellController:
#IBAction func upVoteButtonPressed(sender: AnyObject) {
groupVote[voteScoreLabel.tag]=groupVote[voteScoreLabel.tag]+1
}
What am I doing wrong here so that I can make the UILabel change when its corresponding value in the array has changed.
You need to reload the your tableView data when a button is clicked.
The reloadData() function calls every tableView delegate/data source method again and reload the whole controller if its UITableViewController or the whole tableView if its a view added to another controller
Your code should look like this:
#IBAction func upVoteButtonPressed(sender: AnyObject) {
groupVote[voteScoreLabel.tag]=groupVote[voteScoreLabel.tag]+1
instance_of_your_tableView.reloadData()
// If this is the name of method in swift //
}
Regards
Related
Problem I want to allow users to hit 'swap' in a table cell and then find a different Realm object to populate the 2 text labels (for exercise name and number of reps) in the cell with the values from the new object.
Research There's quite a bit (admittedly old) on 'moving rows' (e.g. here How to swap two custom cells with one another in tableview?) and also here (UITableView swap cells) and then there's obviously a lot on reloading data in itself but I can't find anything on this use case.
What have I tried my code below works fine for retrieving a new object. i.e. there's some data in the cell, then when you hit the 'swapButton' it goes grabs another one ready to put in the tableView. I know how to reload data generally but not within one particular cell in situ (the cell that the particular swap button belongs to... each cell has a 'swap button').
I'm guessing I need to somehow find the indexRow of the 'swapButton' and then access the cell properties of that particular cell but not sure where to start (I've played around with quite a few different variants but I'm just guessing so it's not working!)
class WorkoutCell : UITableViewCell {
#IBOutlet weak var exerciseName: UILabel!
#IBOutlet weak var repsNumber: UILabel!
#IBAction func swapButtonPressed(_ sender: Any) {
swapExercise()
}
func swapExercise() {
let realmExercisePool = realm.objects(ExerciseGeneratorObject.self)
func generateExercise() -> WorkoutExercise {
let index = Int(arc4random_uniform(UInt32(realmExercisePool.count)))
return realmExercisePool[index].generateExercise()
}
}
//do something here like cell.workoutName
//= swapExercise[indexRow].generateExercise().name???
}
Hold your objects somewhere in a VC that shows UITableView. Then add the VC as the target to swap button. Implement swapping objects on button press and reload data of table view after.
The whole idea is to move logic to view controller, not in separate cell.
There are 2 ways.
1. Adding VS as button action target.
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ... // get cell and configure it
cell.swapBtn.addTarget(self, action: #selector(swapTapped(_:)), for: .touchUpInside)
return cell
}
func swapTapped(_ button: UIButton) {
let buttonPosition = button.convertPoint(CGPointZero, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(buttonPosition)!
// find object at that index path
// swap it with another
self.tableView.reloadData()
}
Make VC to be delegate of cell. More code. Here you create protocol in cell and add delegate variable. Then when you create cell you assign to VC as delegate for cell:
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ... // get cell and configure it
cell.delegate = self
return cell
}
func swapTappedForCell(_ cell: SwapCell) {
// the same logic for swapping
}
Solution from OP I adapted the code here How to access the content of a custom cell in swift using button tag?
Using delegates and protocols is the most sustainable way to achieve this I think.
I hope this helps others with the same problem!
I have a button in a cell as a toggle to check in members in a club. When I check in a member, I need the button's state to stay ON after scrolling, but it turns back off. Here is the cellForRow method:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.membersTableVw.dequeueReusableCell(withIdentifier: "CellMembersForCoach", for: indexPath) as! CellMembersForCoach
let member = members[indexPath.row]
cell.setMember(member)
cell.cellController = self
return cell
}
Here is the portion in the custom cell class where I toggle the button
#IBOutlet weak var checkBtn: UIButton!
#IBAction func setAttendance(_ sender: Any){
// toggle state
checkBtn.isSelected = !checkBtn.isSelected
}
The toggling works but after scrolling the table, the button state changes back to original. Any suggestion is appreciated.
This happens because you are reusing the cells.
You need to keep track of which cells have been selected. Perhaps in your member's class. Then when you are in your cellForRowAt you should check if this cell has been selected before and set the correct state for your button.
This is because of tableview is reusing your cell. so you have to maintain button as per tableView data source.
Shamas highlighted a correct way to do it, so I'll share my whole solution.
I created a singleton class to store an array of checked cells:
class Utility {
// Singleton
private static let _instance = Utility()
static var Instance: Utility{
return _instance
}
var checkedCells = [Int]()
In the custom cell class I have action method wired to the check button to add and remove checked cells:
#IBOutlet weak var checkBtn: UIButton!
#IBAction func setAttendance(_ sender: Any){
// Get cell index
let indexPath :NSIndexPath = (self.superview! as! UITableView).indexPath(for: self)! as NSIndexPath
if !checkBtn.isSelected{
Utility.Instance.checkedCells.append(indexPath.row)
}else{
// remove unchecked cell from list
if let index = Utility.Instance.checkedCells.index(of: indexPath.row){
Utility.Instance.checkedCells.remove(at: index)
}
}
// toggle state
checkBtn.isSelected = !checkBtn.isSelected
}
In the cellForRowAt method in the view controller I check if the cell row is in the array and decide if the toggle button should be checked:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.membersTableVw.dequeueReusableCell(withIdentifier: "CellMembersForCoach", for: indexPath) as! CellMembersForCoach
if Utility.Instance.checkedCells.contains(indexPath.row){
cell.checkBtn.isSelected = true
}
return cell
}
The problem is here:
checkBtn.isSelected = !checkBtn.isSelected
This code will reflect the button selection state every time the cell is configured when the delegate cellForRowAt invokes. So if you selected it before, now it turns to not-selected.
Since the tableView is reusing cells you code is not going to work.
You have to keep track of each button when selected and set it again when tableview is reusing cells when you scroll.
Solution : You can take an array(contains bool) which is size of your tableview data.
So you have to set state of button using array and update array when selected or deselected.
Currently I have a few of custom cell's prototypes created in Storyboard with text fields embedded in them. To access these text fields, I use nameTextField = cell.viewWithTag:(1) in cellForRowAtIndexPath:. But viewDidLoad: and viewWillAppear: methods get called before cellForRowAtIndexPath, so at that time nameTextField is nil. To populate text fields when table view shows on screen, I use viewDidAppear:, but it results in a noticeable delay. Also, when I scroll table view up and down, cellForRowAtIndexPath: gets called again and again, resetting already entered data in text fields.
Are there more efficient ways to populate text fields embedded in custom cells' prototypes with data just before the view shows up, and to prevent resetting of entered data in each cellForRowAtIndexPath: call?
I guess you're creating profile screen (or something with many textField to get input data from user). Am I right?
If I'm right, you can use a static tableView (when you have a few textFields)
Hope this can help.
I'm not sure I understand completely what you're trying to do, but cells are normally configured in the cellForRowAtIndexPath: method, not in viewDidLoad. You can also try connecting the textfield to an outlet on your custom cell class. Then you can do:
// in view controller
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
as! CustomCell
let object = myDataSource[indexPath.row]
cell.textField.text = object.description
cell.shouldBecomeFirstResponder = indexPath.row == 0
return cell
}
// then in the cell
class CustomCell: UITableViewCell {
#IBOutlet weak var textField: UITextField!
var shouldBecomeFirstResponder: Bool = false
override func awakeFromNib() {
if shouldBecomeFirstResponder {
textField.becomeFirstResponder()
}
}
}
Then when users input text into the textfield, it would make sense to update your data source.
In viewDidLoad try to run something like self.tableView.reloadData before you do this line "nameTextField = cell.viewWithTag:(1)".
I have an uitableview with a custom cell which gets data from the array.
Custom cell has an uilabel and an uibutton (which is not visible until the uilabel text or the array object which loads for the text - is nil).
On launch everything is fine. When i press the uibutton the array is being appended, the new cells are being inserted below the cell.
But when i scroll - all of a sudden the uibutton appears on other cells where this conditional uilabel text isEmpty is not implied.
Here is how the whole process looks like
Here is my code for cellForRowAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:TblCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! TblCell
cell.lblCarName.text = someTagsArray[indexPath.row]
if let text = cell.lblCarName.text where text.isEmpty {
cell.act1.hidden = false
} else {
println("Failed")
}
cell.act1.setTitle(answersdict[answersdict.endIndex - 2], forState:UIControlState.Normal)
cell.act2.setTitle(answersdict.last, forState:UIControlState.Normal)
return cell
}
So my general question is how do i stop the reuse of those custom cells?
As far as i'm aware there is no direct way of doing this on reusablecellswithidentifier in swift, but maybe there are some workarounds on that issue?
When a cell is reused, it still has the old values from its previous use.
You have to prepare it for reuse by resetting that flag which showed your hidden control.
You can do this either in tableView:cellForRowAtIndexPath: or the cell's prepareForReuse method.
Update:
Here's an example you can add for TblCell:
override func prepareForReuse()
{
super.prepareForReuse()
// Reset the cell for new row's data
self.act1.hidden = true
}
I inserted a UISwitch element in my custom UITableViewCell, and I have over 100 cells in my UITable.
It displays multiple UISwitch that I can tap which one is on and off in the UITable.
But I found something weird.
When I tap the first UISwitch, the 13rd, 26th, 38th, 51st, .... UISwitch changes its status simultaneously.
No matter which section they are, I change one UISwitch status, and another UISwitch changes.
The interval of those changed UISwitch are 11, 12, 11, 12, 11, 12............ elements.
I tap the 13rd UISwitch OFF, and the 1st, 26th, 38th, ..... UISwitch changes to OFF.
I tried to apply Segmented Control instead of UISwitch, and I get the same actions of those elements.
I write these code in my ViewController.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell : CardListCell = tableView.dequeueReusableCellWithIdentifier("RandomCardCell") as CardListCell
var person = cardArray1[indexPath.item]
cell.setCell(person.cardName, leaderName: person.leaderName, cardNo: person.cardNo, groupName: "")
cell.countDelegate = self
//this is my delegate claimed in the Cell, and implemented in the View Controller
return cell
}
In my cell, I create a function to set values of the custom cell.
func setCell(cardName: String, leaderName: String, cardNo: Int, groupName: String)
{
self.cardNameLabel.text = cardName
self.leaderNameLabel.text = leaderName
self.cardNo = cardNo
self.groupName = groupName
}
I did nothing in the UISwitch action now.
#IBAction func switchAction(sender: AnyObject) {
}
Do I miss any thing when I set up my custom cell ??
I just want to tap one, and ONLY ONE UISwitch changes, not multiple ones change.
You need to set the switch either on or off in your cell before you return it from cellForRowAtIndexPath. If you only turn it on and then use a cell returned by tableView.dequeueReusableCellWithIdentifier, you're going to get the cached value when your old cell is re-used.