I've got a tableView in the ViewController & an array called toDo, in the TableView I've a cell and in the cell I got textView.
the textView is editable (The user can change the text in the textView).
After the user changes the text - I want the cell to save it to the toDo array, but whenever I reloaddata the text disappears.
Here is what I tried:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: TableViewCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath:indexPath) as! TableViewCell
cell.textField.text = toDo[indexPath.row]
cell.textField.delegate = self
return cell
}
**I have a got a test button that whenever I click it reload data.
Try this out - set the tag on the text field and implement textFieldDidEndEditing: to update your model before reloading the table view.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: TableViewCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath:indexPath) as! TableViewCell
cell.textField.text = toDo[indexPath.row]
cell.textField.tag = indexPath.row
cell.textField.delegate = self
return cell
}
func textFieldDidEndEditing(textField: UITextField) {
toDo[textField.tag] = textField.text
}
I think that the problem is that, whenever the system needs to re-render the cell, the method func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) is called; that happens before your text view has a chance to save its content in the data model. In your case every time you press the button. I assume you save the content of the text field using optional func textFieldDidEndEditing(_ textField: UITextField) delegate method. You can add a println("SAVING") in such method, and a println("RE-RENDERING CELL") in the tableView(...) method and see the what the sequence of events is. Nots sure if this could help, but I would try that.
Related
In my UITableViewCell have button AddToCart buttons. As if my UITableView data is more than 10 means I have to scroll to see all data. So now if I will on first button of first UITableViewCell as I scroll down to see all records of tableview than automatically last or second last button will also click I am unable to find the problem why this is happening
I am implementing first time this type of functionality so got stuck to resolve the problem
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 13
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tblCell") as! ProductTableViewCell
cell.btnAddToCart.tag = indexPath.row
cell.btnAddToCart.addTarget(self, action: #selector(addToCartDell(sender:)), for: .touchUpInside)
return cell
}
This function is used for hide and show Add To Cart button option.
#objc func addToCartDell(sender: UIButton) {
let tagVal = sender.tag
let indexPath = IndexPath(item: tagVal, section: 0)
if let cell = tblProduct.cellForRow(at: indexPath) as? ProductTableViewCell {
cell.btnAddToCart.isHidden = true
}
}
Cells are reused. You don't save the hidden state of the cell so when a cell is reused the latest state is preserved.
In Swift the most efficient and reliable solution is to save the state added to cart in the data model and use a callback closure to update the UI in cellForRow.
In the data model add a property addedToCart, it's assumed that a custom struct or class is used as data model
var addedToCart = false
In ProductTableViewCell add the callback variable and an IBAction. Connect the IBAction to the button
var callback : (()->())?
#IBAction func buttonPressed(_ sender : UIButton) {
callback?()
}
In the controller in cellForRow handle the callback, products represents the data source array
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tblCell") as! ProductTableViewCell
let product = products[indexPath.row]
cell.btnAddToCart.isHidden = product.addedToCart
cell.callback = {
product.addedToCart = true
cell.btnAddToCart.isHidden = true
}
return cell
}
No tags, no target/action, no protocols, no extra work in willDisplayCell .
This issue is turning up because we re-user same cell for displaying any further rows that was not visible yet.
you may implement this method to correct the display of any further cells
optional func tableView(_ tableView: UITableView,
willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath)
Customize the cell as you want it to appear in this delegate method. This delegate method is called just before the cell is displayed, so you can do any customization here and it will turn up in the UI as per your customization.
If we go deep in to implementation.
There must be a model that keeps the state addedToCart in this model on basis of the button tapped in a particular row and use this same model's addedToCart (model.addedToCart) to show hide the button in delegate method.
My first line of writing
Copy itself to the bottom lines
And when I scroll,
Places of data are changing
Data in UITableView Cell is repeated because Cell is reused so you will need to keep track of data for cell, may be you can add data in array arranged by index.
Yes thats the concept of UITableview dequeCells: as it reuses cells , so if there are say 100 entries only limited number of cells will be created at a time in order to save memory.
Now in order to avoid it, fill your cell views with datasource that has all the values.
If you see that some values are missing from datasource, just use, for example:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! Tek101TableViewCell
cell.selectionStyle = .none
cell.siraLabel.text = ""
cell.siraLabel.text = String(indexPath.row + 1) + ")"
return cell
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! Tek101TableViewCell
cell.selectionStyle = .none
cell.siraLabel.text = String(indexPath.row + 1) + ")"
return cell
}
You need to set value in textfield delegate method as below :(As per your need)
If you are use return button in keyboard
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let cell = textField.superview?.superview as! Tek101TableViewCell // track you view hierarchy
cell. siraLabel?.text = textField.text
textField.resignFirstResponder()
return true
}
If you want to track value end editing method
func textFieldDidEndEditing(_ textField: UITextField){
let cell = textField.superview?.superview as! Tek101TableViewCell // track you view hierarchy
cell. siraLabel?.text = textField.text
}
In UITablview cell is reused so this thing will happen. To solve this issue you have to keep all the records inside one global array variable.
You can store all input in array or dictionary while user insertion. and then you have to give this array values to each and every cell from CellForRow.
In short you have to deal with array only.
Hope this will help you.
I have a tableview where the user is able to select multiple rows (these rows are distinguished by a checkbox in the row). For some reason, however, I can't implement the functionality to deselect any selected row. Can somebody tell me what I'm missing?
SomeViewController.m
#objc class SomeViewController: UIViewController, NSFetchedResultsControllerDelegate, UITableViewDataSource, UITableViewDelegate {
var deviceArray:[Device] = []
// [perform a fetch]
// [insert fetch results into deviceArray to be displayed in the tableview]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customcell", for:
indexPath)
// Set up the cell
let device = self.deviceArray[indexPath.row]
cell.textLabel?.text = device.name
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.tableView(tableView, cellForRowAt: indexPath).accessoryType = UITableViewCellAccessoryType.checkmark
NSLog("Selected Row")
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
self.tableView(tableView, cellForRowAt: indexPath).accessoryType = UITableViewCellAccessoryType.none
NSLog("Deselected Row")
}
}
More info:
Looking at the debug logs inserted, I can share the following observations:
When selecting an unselected row, the console prints "Selected Row"
If I click the same row from observation #1, the console prints "Selected Row" only
If I click on any other row, the console prints "Deselected Row" and then "Selected Row"
If I click on the same row as observation #3, the console prints "Selected Row" only.
So, it looks like everytime I click on a different row, tableView: didDeselectRowAt: gets called; however, checkmarks in the clicked row do not go away.
More Info 2:
So I'm new to storyboards and didn't set the "allowsMultipleSelection" property. Going into the Attributes Inspector, this is what my settings look like:
Now, when pressing the same row in the tableView, my console confirms that the app is alternating between tableView:didSelectRowAt: and tableView:didDeselectRowAt:, however, the checkmark doesn't disappear; once the user selects a row, the checkmark remains selected even when tableView:didDeselectRowAt: is called. What else am I missing?
First off make sure your datasource AND delegate outlets are set if you are setting them from storyboard.
Another thing is you need to set allowsMultipleSelection property to true to get the didSelect, didDeselect methods to get called in the behavior you want. Otherwise it will always call didSelect for the cell you tapped on and didDeselect for the most previously selected cell.
The other thing to note is that you are referencing self.tableView when setting your cell.accessoryType property. This may be different instance of the tableView being passed into the delegate method. I recommend a guard let statment to ensure the code setting the accessory type only applies if the tableView being passed into the function. Here is code I used to get it to work.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Notice I use tableView being passed into func instead of self.tableView
guard let cell = tableView.cellForRow(at: indexPath) else {
return
}
cell.accessoryType = .checkmark
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) else {
return
}
cell.accessoryType = .none
}
If you want user to be able to select multiple cells, you need to set tableView.allowsMultipleSelection = true in the viewDidLoad method or set multiple selection in the attributes inspector.
I have a custom cell with a UITextView in it and outlet for that UITextView is in the customCell file.
Now in my viewController I have tableView and I create and add that custom cell into my table.
After this I can't access UITextView and get its data as its outlet in the customCell file. I can't move the outlet to my viewController as I need it there.
How can I do this (access the UITextView in my viewController)?
You can set that delegate of cell's textView with your viewController, First set its delegate inside your cellForRowAtIndexPath
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! CustomTableCell
cell.textView.delegate = self
cell.textView.tag = indexPath.row //In case you have multiple textView
return cell
}
Now you can get this textView inside its UITextViewDelegate method.
func textViewDidBeginEditing(textView: UITextView) {
print(textView.text)
}
You need to declare a local variable in your viewController to get the data of your UITextView. Then implement the tableViews delegate method
//this is your local variable declared in viewCOntroller
var myTextViewText: String?
//And this is your delegate method
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! YourCell
myTextViewText = cell.textView.text //here you will get the text of your cell's textView
}
I am trying to create a segue happen when a cell has been selected. I have tired using cell.dequeueReusableCellWithIdentifier(""). However it is returning "nil" whilst unwrapping. I have set up the cells ID correctly and they match. Any help is greatly appreciated!!
if menuTableView.dequeueReusableCellWithIdentifier("") == "logout" {
print("logout")
performSegueWithIdentifier("logoutSegue", sender: self)
}
Thanks in advance
There is a UITableView delegate method for when a user selects a cell, this is good for knowing when a user has selected a cell, but we need to identify if it is the logout cell that has been pressed.
To identify the cell we'll be setting the tag property of the your logout cell.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
//this is the indexPath row where we want our login cell to be showed
let loginCell = tableView.dequeueReusableCellWithIdentifier("login", forIndexPath: indexPath) as! LoginTableViewCell
//set the tag so when we select the cell we can see if the cell we have selected has a tag of 5
loginCell.tag = 5
return loginCell
}else {
//here goes our other cells, in this case they'll just be normal UITableViewCell
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
return cell
}
}
In our cellForRowAtIndexPath delegate method we'll instantiate the loginTableViewCell in the first row and set its tag to 5, if the row isn't 0 we simply return our normal cell
So now we have a cell where the tag is 5 and all the other cells do not have a default tag property of 0, now, when the user selects a cell we can check for this method in the didSelectRowAtIndexPath delegate method of our table view.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)!
if cell.tag == 5 {
//cell is login cell
//perform segue here
}
}
This delegate method gives us the table view and the indexPath of the selected cell. Now we call CellForRowAtIndexPath on the table view to get the cell that was selected. Now that we have the cell we can compare the cell's tag. If the tag is 5 the logout cell was selected so we can perform our segue there.