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.
Related
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.
I am trying to work out how to action buttons within a UITableViewCell when the table view is 'top down' - as in, each new row is added to the top of the table view. I achieve this with the following code:
I use the following code to insert new items into my model array and then into the tableview:
let newItem = Item(text: inputTextView.text, time: Date())
items.insert(newItem, at: 0) //inserting into the front of model array
tableView.beginUpdates()
let indexPath:IndexPath = IndexPath(row:0, section:0)
tableView.insertRows(at: [indexPath], with: .fade)
tableView.endUpdates()
Within the cellForRowAt function I run the following code:
let cell = tableView.dequeueReusableCell(withIdentifier: postCellID, for: indexPath) as! NewPostCell
let item = items[indexPath.row]
cell.postTextLabel.text = text
cell.timeLabel.text = dateFormatter.string(from: time)
cell.selectionStyle = .none
return cell
Each of my tableview cells have three buttons in it.
How do I connect up these buttons so I know which button is pressed from which indexPath?
The problem is that if I use indexPath.row to tag the buttons, then the buttons in all cells gets tagged with 0, as each insert is happening at the top of the table at indexPath.row 0th position.
I thought of tagging the buttons with the current size of my model array, but that doesn't work either as when cells are re-used they could then be tagged with the length of the array at that point, which would be wrong.
There are a lot of apps that have 'last entry at the top' of the tableview sort of setup, with buttons in cells. So there must be a way to do this.
You can add UIButton to your UITableViewCell and access these UIButton via tag and add target method to these buttons as:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// create a new cell if needed or reuse an old one
let cell:UITableViewCell = self.tableVw.dequeueReusableCell(withIdentifier: "Cell") as UITableViewCell!
//Access UIButton
let button1:UIButton = cell.viewWithTag(10) as! UIButton
let button2:UIButton = cell.viewWithTag(11) as! UIButton
let button3:UIButton = cell.viewWithTag(12) as! UIButton
//Add Action Methods to UIButtons
button1.addTarget(self, action: #selector(FisrtButtonClick), for: .touchUpInside)
button2.addTarget(self, action: #selector(SecondButtonClick), for: .touchUpInside)
button3.addTarget(self, action: #selector(ThirdButtonClick), for: .touchUpInside)
return cell
}
Button Actions
// MARK: - UIButton Methods.
func FisrtButtonClick(_ sender: Any) {
//Get Button cell position.
let ButtonPosition = (sender as AnyObject).convert(CGPoint.zero, to: tableVw)
let indexPath = tableVw.indexPathForRow(at: ButtonPosition)
if indexPath != nil {
print("Cell indexPath: \(indexPath?.row)")
}
}
func SecondButtonClick(_ sender: Any) {
//Get Button cell position.
let ButtonPosition = (sender as AnyObject).convert(CGPoint.zero, to: tableVw)
let indexPath = tableVw.indexPathForRow(at: ButtonPosition)
if indexPath != nil {
print("Cell indexPath: \(indexPath?.row)")
}
}
func ThirdButtonClick(_ sender: Any) {
//Get Button cell position.
let ButtonPosition = (sender as AnyObject).convert(CGPoint.zero, to: tableVw)
let indexPath = tableVw.indexPathForRow(at: ButtonPosition)
if indexPath != nil {
print("Cell indexPath: \(indexPath?.row)")
}
}
Main idea
These are the basic steps you have to do:
Set unique tag values for each of the 3 button types: this will allow to identify which of the 3 buttons of a cell is pressed. (for instance tag value 1 for the first of your buttons, value 2 for the second kind, value 3 for the third button).
Link the 3 buttons to a #IBAction method.
You can also do this programmatically with target-actions: call aButton.addTarget(target:, action:, for:) for each button.
Then when a button will be pressed, you will use an helper function to determine the cell index of the button that was pressed.
Code
The #IBAction method should look like #IBAction func buttonTrigerred(_ sender: UIButton){...} and the code would be:
#IBAction func buttonTrigerred(_ sender: UIButton){
// -- retrieving the index path of the cell, as #NikhleshBagdiya posted
// Determine the indexPath of the cell
let buttonPosition = (sender as AnyObject).convert(CGPoint.zero, to: tableView)
let cellIndexPath = tableView.indexPathForRow(at: buttonPosition)
// --
// Determine which button is called
if sender.tag == 1 { ... } // the first kind of button has been pressed
else if sender.tag == 2 { ... } // second button kind
else if sender.tag == 3 { ... } // third button kind
}
Setting the tag value
Each of my tableview cells have three buttons in it.
I suppose you have designed your UITableViewCell in a Storyboard or Xib file. So for each of the 3 buttons of your cell: select the button, go to the attributes inspector, set the tag to the custom values indicated above.
I have a tableview with multiple sections each section is having 3 cells , each cell contains the custom check mark button. Where user can change check and uncheck images of check button on click.
The problem is, i am changing the cell button image from uncheck to check when user click on button which is working fine. If i scroll the tableview that check mark image is adding to wrong cell.
I googled it and found the solution like saving clicked cell indexPath.row in array and removing from array if user click again on same cell.
It is not working as i have multiple sections.
Please provide me any suggestion to find out the solution for my problem.
// Button action.
func buttonTappedOnCell(cell: customeCell) {
let indexPath : IndexPath = self.tableView.indexPath(for: cell)!
if self.selectedIndexPath.count > 0 {
for selectedIndex in self.selectedIndexPath {
if selectedIndex as! IndexPath == indexPath {
self.selectedIndexPath.remove(indexPath)
} else {
self.selectedIndexPath.add(indexPath)
}
}
} else {
self.selectedIndexPath.add(indexPath)
}
Code in cellForRowAtIndexPath
for anIndex in self.selectedIndexPath {
if anIndex as! IndexPath == indexPath {
cell.checkMarkButton.setBackgroundImage(UIImage(named: "checkMark"), for: .normal)
} else {
cell.checkMarkButton.setBackgroundImage(UIImage(named: "UnCheckMark"), for: .normal)
}
}
You can simplify your code greatly by using a Set<IndexPath>. There is no need to loop through an array.
var selectedPaths=Set<IndexPath>()
func buttonTappedOnCell(cell: customeCell) {
if let indexPath = self.tableView.indexPath(for: cell) {
var image = UIImage(named: "CheckMark")
if self.selectedPaths.contains(indexPath) {
image = UIImage(named: "UnCheckMark")
self.selectedPaths.remove(indexPath)
} else {
self.selectedPaths.insert(indexPath)
}
cell.checkMarkButton.setBackgroundImage(image, for: .normal)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "identifier", for: indexPath) as! YourCellType
...
var image = UIImage(named: "UnCheckMark")
if self.selectedPaths.contains(indexPath) {
image = UIImage(named: "checkMark")
}
cell.checkMarkButton.setBackgroundImage(image, for: .normal)
return cell
}
This happens because the cells of a tableView are reused to minimise memory usage.
If you are using a custom implementation of check button then you have to store the values for the cell which is selected in a data Structure like an array for example if you have 2 sections with 3 elements in each section with 2nd item in 1st section selected, then the data structure like an array can be like [[false, true, false], [false, false. false]].
Here I am setting true in case the cell is selected. Update the values of this data structure and check from it when applying the image in the cellForRowAt and apply the checkedImage only when the indexPath.section and indexPath.row for Array return a true Bool.
Hope this helps. Feel free to reach out in case of any doubts. Happy coding.
inside of your TableView DidSelect method just put
let cell = tableView.cellForRowAtIndex(indexPath).accessoryType = UITableViewCell.accessoryType = Checkmark
vice versa for DidDeselect method but change the Checkmark into None.
let cell = tableView.cellForRowAtIndex(indexPath).accessoryType = UITableViewCell.accessoryType = None
If you create more complex code for a simple method, It'll give you disaster.
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.
When I look to run this on my phone, no checkmark is assigned to the cell after I segue back and forth from my detailViewController. They currently have no checkmark assigned, so I am looking to assign a checkmark to the cell once it has been selected. PLease let me know if you know what is going on here! Thanks
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
if cell.accessoryType == UITableViewCellAccessoryType.None
{
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
}
}
Currently you are dequeuing a new cell, not necessarily the one being selected. However,you should persist whether or not the cell is checked using a model object, not persist state within the view itself. Once the cell is dequeued/reused, it'll no longer be selected.
For example, you'd have an array of model objects:
class YourObject {
var isSelected: Bool = false
}
var modelObjects: [YourObject] = []
Inside each object there would be a property representing whether or not the object is selected, this would be set inside didSelectRowAtIndexPath.
var object = self.modelObjects[indexPath.row]
object.isSelected = true
tableView.reloadRowsAtIndexPaths([indexPath], rowAnimation: .Automatic)
Inside cellForRowAtIndexPath you can check if the property on the model object is selected, if so, set the cell's accessoryType to .Checkmark.
var object = self.modelObjects[indexPath.row]
if object.isSelected {
cell.accessoryType .Checkmark
}
The problem is that dequeueReusableCellWithIdentifier dequeues a cell that is not being currently displayed by the tableView, it does not return the cell that it's being displayed at that index path.
What you want to use is cellForRowAtIndexPath, which will give you the cell for that index path if it's being displayed by the tableView.
If you set the accessoryType to that cell, it will update the visible cell.
The way you are doing, you are dequeuing a new cell instead of updating the cell that is being selected. In order to achieve what you want you shoul replace this let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) with let cell = tableView.cellForRowAtIndexPath(indexPath).
We must handler the 'if' and 'else' both.
In 'cellForRowAtIndexPath':
private var selectedIndex: Int = -1
if selectedIndex == indexPath.row
{
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
}
else
{
cell.accessoryType == UITableViewCellAccessoryType.None
}