Text is replacing while scrolling tableview - ios

I need to increment decrement label value of a particular row. I am using following method, it is working but when I scroll the table view then values are replacing with other's row.
Like I am showing two row on a screen according to design so while scrolling, first row's label value is replacing with forth row label and so on.
#IBAction func plusBtnAction(_ sender: UIButton) {
var cell:TableViewCell = self.tebleView.dequeueReusableCell(withIdentifier: "Cell")! as UITableViewCell as! TableViewCell
let indexPath = IndexPath(row: sender.tag, section: 0)
cell = (self.tebleView.cellForRow(at: indexPath) as? TableViewCell)!
if cell.tag == sender.tag {
print(sender.tag)
print(cell.countLbl.tag)
cell.countLbl.text = "\(Int(cell.countLbl.text!)! + 1)"
}
}

Remember, you are reusing the cells - dequeueReusableCell each time you need to display a new cell, so what's happening here is the first row disappears and when it is redisplayed, you are using the cell that was used for row 4.
The solution to this is to keep a track of your counter in an array outside of the tableView (table, not teble :-) ) Something like this...
var tableDataCounters : [Int] = []
// set up your data ...
cell.countLbl.text = "\(tableDataCounters[indexPath.row] + 1)"

By using dequeueReusableCell(withIdentifier: ) method of UITableView, you are initially creating cells and later on reusing those created cells. You are facing this problem due to this. Since you have only one row whose text you need to change, it's better to keep the text for that row stored externally. For the plusBtnAction, it's better to do the following:
var cellLabelTextArray: [String] = [] // populate this with data for each cell
#IBAction func plusBtnAction(_ sender: UIButton) {
var cell:TableViewCell = self.tebleView.dequeueReusableCell(withIdentifier: "Cell")! as UITableViewCell as! TableViewCell
let indexPath = IndexPath(row: sender.tag, section: 0)
var cellLabelText = cellLabelTextArray[indexPath.row]
cellLabelText = "\(Int(cellLabelText) + 1)"
cellLabelTextArray[indexPath.row] = cellLabelText
if let visiblePaths: [IndexPath] = self.tableView.indexPathsForVisibleRows {
if visiblePaths.contains(indexPath) {
self.tableView.reloadRows(at: [indexPath], with: UITableViewRowAnimation.automatic)
}
}
}
In the above code, we are checking if the indexPath is visible. If it is, then reload that particular indexPath. Otherwise, we don't need to.

Related

How to reload selected cell in particular section for expand/collapse in tableview Swift

I am doing expand/collapse tableview cells feature in my iOS app. I have multiple sections. And each section has multiple cells. By default, cell height is 100, once user taps on cell, I am increasing height to 200.
So, Based on Bool value, I am changing it. But, While scrolling tableview, It is interchanging the expanded/collapse cells in between sections.
Like if I tap on first section first cell, It is expanding, but after scrolling tableview, Second section first cell also expanding.
My Requirement is, If user tap on particular cell, that cell only should expand/collapse. User can manually expand and close. User can expand multiple cells.
So, I have tried to store Indexpath row and Section.
var expandedIndexSet : IndexSet = []
var expandedIndexSection : IndexSet = []
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:"cellIdentifier", for:
indexPath) as! MyTableViewCell
if expandedIndexSet.contains(indexPath.row) && expandedIndexSection.contains(indexPath.section) { // expanded true
cell.height = 200
//some other data loading here
}
else { //expanded false
cell.height = 100
}
}
#IBAction moreButtonTapped(_ sender: Any) {
if(expandedIndexSet.contains(indexPath.row)) && expandedIndexSection.contains(indexPath.section){
expandedIndexSet.remove(indexPath.row)
expandedIndexSection.remove(indexPath.section)
} else {
expandedIndexSet.insert(indexPath.row)
expandedIndexSection.insert(indexPath.section)
}
entriesTableView.beginUpdates()
entriesTableView.reloadRows(at: [indexPath], with: .none)
entriesTableView.endUpdates()
}
Anyone can give better approach than this?
If you store section and row independently in separate arrays, your algorithm will fail.
The reason is that both are dependent:
Think of three expanded cells (row:1, section:1), (row:2, section:1), (row:3, section:2)
Now what happens for the cell (row:3, section:1)?
The row-array contains the value "3", and the section-array contains value "1", therefore it will be considered as expanded.
Therefore, you need to store the index path as a whole - see the sample code:
var expanded:[IndexPath] = []
expanded.append(IndexPath(row:1, section:1))
expanded.append(IndexPath(row:2, section:1))
expanded.append(IndexPath(row:3, section:2))
let checkPath = IndexPath(row:3, section:1)
if (expanded.contains(checkPath)) {
print ("is expanded")
} else {
print ("collapsed")
}
Update
So in your button handle, you'll do the following:
#IBAction moreButtonTapped(_ sender: Any) {
if(expanded.contains(indexPath)) {
expanded.removeAll { (checkPath) -> Bool in
return checkPath == indexPath
}
} else {
expanded.append(indexPath)
}
entriesTableView.beginUpdates()
entriesTableView.reloadRows(at: [indexPath], with: .none)
entriesTableView.endUpdates()
}

How to access textview value which is inside tableview cell in swift 5?

I have one viewcontroller, inside that I have one table view and 2 buttons save and cancel.
In tableview cell i have one textview. after adding some text in textview i want to show that text. I am not sure how to get that tableview text on save button click. (number of rows may be dynamic).
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableview.dequeueReusableCell(withIdentifier: "SummaryManualEditContentCell", for: indexPath) as? SummaryManualEditTableCell {
cell.txtAnswers.text = "enter text here"
return cell
}
return UITableViewCell()
}
#IBAction func btnSave(_ sender: Any) {
print("textviewText1 + textviewText2 + and so on ")
}
In Addition to this on button click i want to add all that text multiple textviews into one string.
is there any clean and best way to achieve this?
Thank you for helping!
You need to get the indexPath of the cell whose text you want to get
get the cell for that indexpath like
#IBAction func btnSave(_ sender: Any) {
let indexPath = IndexPath(row: 0, section: 0)
if let cell = tableView.cellForRow(at: indexPath) as? SummaryManualEditTableCell {
let text = cell.txtAnswers.text
}
}
And if you have multiple cells with textFields you can loop around to get all fields
#IBAction func btnSave(_ sender: Any) {
var allTextViewsText = ""
for i in 0...5{
let indexPath = IndexPath(row: i, section: 0)
if let cell = tableView.cellForRow(at: indexPath) as? SummaryManualEditTableCell {
allTextViewsText += cell.txtAnswers.text
}
}
print(allTextViewsText)
}
But keep it in mind that this approach only works in the case of visible cells otherwise for non visible cells you will get nil
I will suggest you to implement textView:shouldChange in each cell who has textView with a delegate to the tableView's viewController. When text is changed in the cell, delegate should propagate the change to the viewController which will save the value in a variable.
Then, when you press on the save button, you would simply take values from variables.

increase cell count without increasing data in table of iOS

my data set is like
var data = [["1"],["2","3"],["4"]]
and I want to show it in table view
which I am able to using
cell.textlabel?.text = data[indexPath.section][indexPath.row]
I have a button in navigation bar which when I click should increment all the sections row by 1 without changing anything in data
and all the newly created cell textfield should say "add here"
my cell for rowat
let cell = table.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textlabel?.text = data[indexPath.section][indexPath.row]
return cell
how should I change my cell for row at so that I don't get fatal error index out of range
Adding to #iOSArchitect.com 's answer,
You should check the index in cellForRowAt.
The reason you are getting index out of range exception is because your numberOfCell count increases but the cell's data source remains the same.
So,
let cell = table.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if indexPath.row < data[indexPath.section].count{
cell.textlabel?.text = data[indexPath.section][indexPath.row]
}else{
cell.textlabel?.text = "add here"
}
return cell
This may not be the precise code but I hope you get the logical answer out of this.
Declare a variable named extraRows and assign its initial value as 0 and add a property observer to it so that whenever it changes, it reloads the tableView
var extraRow : Int = 0 {
didSet {
self.tableView.reloadData()
}
}
After the click of the add button, update extraRow to + 1
#IBAction func addBtnClicked(_ sender : Any) {
extraRow = extraRow + 1
}
Also, update your number of rows
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count + extraRows
}
Along with this, you also need to update your tableview cellForRowAtIndexPath and place a check for if indexPath.row > data.count , then show your empty celll

how to store tableview cell checkmark state in popover?

I am having few buttons in initial view controller. A popover with an expandable tableview will appear on click of any button. I am using single popover for all the buttons with different data to be shown on tableview. The selected items in the popover when I am clicking on the first button is being removed when I am clicking on the other buttons so as to all. The selected items is gonna saved into an array based on indexpath.row. An exception error is coming while I am trying to remove the selected items from the same array based on same indexpath.row.
How can i store the checkmark on each cell selection for all popover ?
Here is a piece of my code
func expandableTableView(_ expandableTableView: LUExpandableTableView, didSelectRowAt indexPath: IndexPath)
{
let selectedService = arrData[indexPath.section].Children?[indexPath.row].EnglishName ?? ""
let inPrice = arrData[indexPath.section].Children?[indexPath.row].InPrice ?? 0
print("service and price : \(selectedService) \(inPrice)")
let selectedItem = (" \(selectedService) \(inPrice)")
let cell: UITableViewCell? = expandableTableView.cellForRow(at: indexPath)
if cell?.accessoryType == UITableViewCellAccessoryType.none
{
cell?.accessoryType = UITableViewCellAccessoryType.checkmark
selectionArr.append(selectedItem)
inPriceCount = inPrice
}
else
{
cell?.accessoryType = UITableViewCellAccessoryType.none
selectionArr.remove(at: indexPath.row)
inPriceCount = -inPrice
}
self.delegate?.messageData(data: selectionArr as AnyObject)
self.delegate?.inPrice(data: inPriceCount as AnyObject)
let rowToSelect = [expandableTableView .indexPathForSelectedRow]
print(rowToSelect)
}
You should not remove any thing from array. Just add a key on every index
dict["ischeckMarked"]=false
When you change tap on checkmark change it true
dict["ischeckMarked"]=true
If already checked and now you want to unmark change it to false
dict["ischeckMarked"]=false
Hope this will help you out.

ios swift: UITableViewCell's tag is not updated after consecutive delete of cells

I have implemented the delete functionality for any row based on the index passed.
Each cell has a button to initiate delete for that row. I take the cell.tag to detect the row and pass to delete function which uses indexPath and deleteRowAtIndexPaths(...).
Now, the problem happens when I keep on deleting the 0th row. Initially, it deletes correctly. 0th row is gone. 1st row replaces the 0th row.
Now, if I delete 0th row again, it deletes the current 1st row.
The reason I understood is that cell.tag is not updated.
What exactly an I doing wrong ?
The problem is not consistent. If I wait between the deletes, it is ok. If I delete one row after another. It keeps on deleting some other row.
How should I proceed now ? I have searched for this already and unable to find proper solution or guide ?
Here are the main pieces of code
// Typical code having Programmatic UITableView
// ...
func addTestEvent(cell: MyCell) {
func onSomeAction() {
dispatch_async(dispatch_get_main_queue(), {
self.removeRow(cell.tag)
})
}
...
// onSomeAction() called on click on the button
}
func test(cell: MyCell) -> () {
...
addTestEvent(cell)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier( NSStringFromClass(MyCell), forIndexPath: indexPath) as! MyCell
cell.tag = indexPath.row
cell.test = { (cell) in self.test(cell) }
return cell
}
func removeRow(row: Int) {
let indexPath = NSIndexPath(forItem: row, inSection: 0)
tableView.beginUpdates()
posts.removeAtIndex(row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
tableView.endUpdates()
}
The key point is not to use cell.tag to identify the cell. Rather use the cell directly. Thanks Vadian for the comment. It is not a good practice to keep indexPath in cell tag. Now I know why !
This answer gave me the major hint to resolve the problem.
https://stackoverflow.com/a/29920564/2369867
// Modified pieces of code. Rest of the code remain the same.
func addTestEvent(cell: MyCell) {
func onSomeAction() {
dispatch_async(dispatch_get_main_queue(), {
self.removeRow(cell)
})
}
// ...
// onSomeAction() called on click on the button
}
func removeRow(cell: UITableViewCell) {
let indexPath = tableView.indexPathForRowAtPoint(cell.center)!
let rowIndex = indexPath.row
// ...
}
Add tableView.reloadData() after deleting a cell. That worked for me.

Resources