Populating text field in prototype cell - ios

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)".

Related

swift textview and labels return empty text in UITableviewcell

Current Situation:
I have a UITableView with custom cells. In the cells are 1 label and 1 textview.
Scrolling is enabled for the UITableView.
Below the Table, I have a button to save the entries from the textview.
My Problem is:
I get only the value from the first row.
From the other rows, I get always an empty text, but only if the user has scrolled.
I don't understand why and I hope you can help me.
Here is my Code:
#objc func save()
{
for i in 0..<labels.count
{
let indexPath = IndexPath(item: i, serction: 0)
let cell = self.SearchTable.dequeueReusableCell(withIdentifier: "searchCell",
for: indexPath) as! SearchCell
print("Index: \(indexPath)")
if(cell.EditField.text = ""
{
continue;
}else{
...
}
}
}
Debugger
First Row: FirstRow
Other Rows: SecondRow
Cells in a tableview are reused, so a cell at an index path that is not visible could have values from a completely different cell. If you want to get all the cells that are currently visible on the screen, you could use tableView.visibleCells docs to get all the cell that are currently on screen and extract data from there.
Alternatively, you could choose to not implement cell reuse and make your table view static. You can do this in Interface Builder, or you could also choose to create all the cells up front and return your pre-made cells in tableView(_:cellForRowAt:). Note that a setup like this is okay for small datasets, but has terrible performance for larger sets so be aware that this might not be the best way to do things. It really depends on your situation. This method of doing things would end up looking a bit like this:
var cells = [UITableViewCell]()
override func viewDidLoad() {
// do all kinds of stuff
for field in fields { // or whatever else mechanism you use as your datasource
let cell = UITableViewCell()
// configure your cell
cells.append(cell)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return cells[indexPath.row]
}
The third and last way you might want to solve this is to add a delegate to your cells and set the view controller as the delegate. If the text changes, call the delegate with the cell's index path and the new text value. You can then store this text somewhere in the view controller and read it from there when you save rather than pulling it from the cell's textfield. Personally I would prefer this method.
Rather than iterate over the cells in the tableview, you could just get the data from the data source.
Whenever the text in the cell's text field changes you could update the data source and then use the information from there to perform your save.
You must be having some kind of data source anyway, otherwise what happens to the text when the cell scrolls off the screen and comes back on again? If you aren't storing the text somewhere then you've got nothing to populate the cell with in the table views cellForRow(... delegate method.

Swap and reload data in tableView for different Realm object

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!

UITableViewCell Data changes after scrolling

I am creating a form in UITableViewCell, this form has lable and text field, in prototype cell. See its image here
TableView Story board Image.
I am using identifier to dynamically create the form, my code is
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("protocell", forIndexPath: indexPath) as! UITableViewCell
var lable = cell.viewWithTag(1) as! UILabel
lable.text = details[indexPath.row]
lable.textColor = UIColor.brownColor().colorWithAlphaComponent(0.7)
var textfield = cell.viewWithTag(2) as! UITextField
textfield.placeholder = "Enter \(details[indexPath.row])"
self.arrayTextField.append(textfield)
if details[indexPath.row] == "Name" {
textfield.placeholder = "Enter First \(details[indexPath.row]) Middle \(details[indexPath.row]) Last \(details[indexPath.row]) "
}
textfield.backgroundColor = UIColor.redColor().colorWithAlphaComponent(0.2)
return cell
}
Now the problem is I am having 18 fields and when I enter values in the field and scroll the the view to fill the remaining fields, the values changes in the fields changes.
Please help.
Create UITableViewCell subclass and override prepeareForReuse function - to turn cell to default mode.
Swift:
override func prepareForReuse() {
super.prepareForReuse()
//set cell to initial state here, reset or set values, etc.
}
As per your comment - of how to subclass UITableViewCell:
import UIKit
class MyCustomCell: UITableViewCell {
// things that you can do here:
// initialise your properties here. (label, textfield. etc)
// layout subviews.
// override superclass APIs.
}
Do not store the textfields in an array instead datasource "details" array should do it.
Once you have entered any thing in the textfield update you "details" array for that indexPath.
Subclass UITableViewCell and make a CustomTableViewCell and have IBOutlets for you textfields labels and what not to make your life easier
Have a method on your CustomTablewViewCell like updateWithDetails(details) so that the code is well encapsulated

Extremely weird behavior with table cells when going off screen

Here is the really weird behavior. When the table view is first loaded, it looks like this:
Now, when I scroll down and then scroll back up, the buttons appear on cells that didn't have buttons on them before! Like so:
I know this has to do with "This is the intended behaviour of a UITableView. The whole point of a UITableView is to queue up cells that are needed and release cells that aren't needed to manage memory" as described in this post: UITableView in Swift: Offscreen cells are not pre-loaded.
Here is my code:
var messages = [String]()
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CathyTaskLogTableViewCell
if messages[indexPath.row] != "" {
cell.messageButton.hidden = false
}
return cell
}
Anybody have a solution to this problem?
The reason for getting this result is because of UITableViewCell is being reuse.
if messages[indexPath.row] != "" {
cell.messageButton.hidden = false
}
else
{
cell.messageButton.hidden = true
}
There are two possible solutions to your problem:
Always set the hidden property:
cell.messageButton.hidden = messages[indexPath.row] != ""
Reset the state of your cell when it is reused, this provides deterministic behaviour in the table view controller, without burdening the controller class with tasks that a cell should do. This can be done by overwriting prepareForReuse in CathyTaskLogTableViewCell.
func prepareForReuse() {
super.prepareForReuse()
self.messageButton.hidden = true // or false, depending on what's the initial state
// other stuff that needs reset
}
With the current code, messageButton gets hidden only if the cell doesn't find something in messages. So for cells that had this button visible, were reused, and now correspond to a cell that doesn't have a correspondent in messages, the button will remain visible.

Stop the reuse of custom cells Swift

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
}

Resources