I'm trying to find a UITableViewCell text in an array. The problem is no matter what cell I'm clicking on, array.index(of:) always returns 1.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if let text = cell.textLabel?.text {
let indexOfCellString = event.index(of: text)
print (indexOfCellString!)
}
}
//here is my attempt to set the cell text:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if eventIndex.indices.contains(indexPath.row) == true {
cell.textLabel?.text = event [indexPath.row]
}
return cell
}
event is an array that i'm trying to search in and find cell's text.
and cell's text is always in an index of event. because my tableview reads the information from event array. therefore it shouldn't always return 1.
What am I doing wrong?
Never use dequeueReusableCell outside of cellForRowAt. Simply access your data from your data model.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if eventIndex.indices.contains(indexPath.row) == true {
let text = event[indexPath.row]
if let indexOfCellString = event.index(of: text) {
print("Found index: \(indexOfCellString)")
} else {
print("text not found")
}
}
}
Related
I have the below code and I want to print the text of the selected cell ( a custom cell with a text label )
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DateCell", for: indexPath) as! DateCell
cell.dateLabel.text = objectsArray[indexPath.section].sectionObjects[indexPath.row]
cell.selectionStyle = .none
contentView.separatorStyle = .singleLine
contentView.allowsSelection = true
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if contentView.cellForRow(at: indexPath)?.accessoryType == UITableViewCell.AccessoryType.none{
contentView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
else{
contentView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
}
I already tried adding the below code in didSelect row at but I get nil.
print ((contentView.cellForRow(at: indexPath)?.textLabel?.text)!)
Any ideas on how I can do this?
Get it from the original source...
func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
let str = objectsArray[indexPath.section].sectionObjects[indexPath.row]
print(str)
}
Since you are setting text from the data source, when cell is selected, you can check the index in your data source
if let text = objectsArray[indexPath.section].sectionObjects[indexPath.row]{
//Do Something
}
I have model data, which has a boolean value that says if checkmark should be shown (accessoryType is .checkmark)...
So for example, I want to have two of five rows checkmarked at start (based on my model as I said)... The thing is, I am able to show checkmarks, but after I tap on them, toggling doesn't work right:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .checkmark
model[indexPath.row].isCellSelected = true
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .none
model[indexPath.row].isCellSelected = false
}
}
And here is a cellForRowAt:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let data = model[indexPath.row]
let identifier = data.subtitle != nil ? kSubtitleID : kNormalID
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
cell.textLabel?.text = data.title
cell.detailTextLabel?.text = data.subtitle
return cell
}
I am able to show check mark like this:
cell.accessoryType = data.isCellSelected ? .checkmark : .none
But when I tap on it, it cause it is selected (allowsMultipleSelection is set to true), it doesn't get toggled, but rather stays for the first time.
Here is the model I use. It is really simple:
struct CellModel{
var title:String?
var subtitle:String?
var isCellSelected:Bool = false
}
You should perform toggling in tableView:didSelectRowAt: only, and reload the necessary cell afterwards. You should omit tableView:didDeselectRowAt:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
// Toggle the value - true becomes false, false becomes true
model[indexPath.row].isCellSelected = !model[indexPath.row].isCellSelected
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .none)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let data = model[indexPath.row]
let identifier = data.subtitle != nil ? kSubtitleID : kNormalID
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
cell.textLabel?.text = data.title
cell.detailTextLabel?.text = data.subtitle
// Set the checkmark or none depending on your model value
cell.inputAccessoryType = data.isCellSelected ? .checkmark : .none
return cell
}
Edit:
Use this for single selection only + ability to deselect selected item:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Loop through all model items
for (index, item) in model.enumerated() {
if (index == indexPath.row) {
// Toggle tapped item
item.isCellSelected = !item.isCellSelected
} else {
// Deselect all other items
item.isCellSelected = false
}
}
tableView.reloadData();
}
You dont have to use select and deselect until you have a model that saves the boolean value
In tableview didselect method
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if model[index.row].isCellSelected == true {
model[indexpath.row].isCellSelected = false
}
else {
model[indexpath.row].isCellSelected = true
}
tableview.reloadData
}
And in cell for row check
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if model[index.row].isCellSelected{
cell.accessoryType = .checkmark
}
else {
cell.accessoryType = .none
}
}
What I did to make all work is:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let data = model[indexPath.row]
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .checkmark
model[indexPath.row].isCellSelected = true
self.selectedData?(model.filter{$0.isCellSelected})
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let data = model[indexPath.row]
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .none
model[indexPath.row].isCellSelected = false
self.selectedData?(model.filter{$0.isCellSelected})
}
}
and
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let data = model[indexPath.row]
let identifier = data.subtitle != nil ? kSubtitleID : kBasicID
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
cell.textLabel?.text = data.title
cell.detailTextLabel?.text = data.subtitle
cell.accessoryType = data.isCellSelected ? .checkmark : .none
return cell
}
but I had to initially setup which cells are selected actually:
func setSelectedCells(){
let selectedIndexes = self.model.enumerated().compactMap { (index, element) -> IndexPath? in
if element.isCellSelected{
return IndexPath(row: index, section: 0)
}
return nil
}
selectedIndexes.forEach{
selectRow(at: $0, animated: false, scrollPosition: .none)
}
}
and then called this in viewDidAppear, cause I had to be sure that table has drawn its content (cause this will fail if we try something (cell) that doesn't exist yet). Not a best way, but it solved the issue for single and multiple selections with initial states.
Then, cellForRowAt method becomes as simple as:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let data = model[indexPath.row]
let identifier = data.subtitle != nil ? kSubtitleID : kNormalID
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
cell.textLabel?.text = data.title
cell.detailTextLabel?.text = data.subtitle
cell.accessoryType = data.isCellSelected ? .checkmark : .none
return cell
}
Then, didSelectRowAt method becomes as simple as:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if let cell = tableView.cellForRow(at: indexPath) {
let isCellSelected = !(model[indexPath.row].isCellSelected)
cell.accessoryType = isCellSelected ? .checkmark : .none
model[indexPath.row].isCellSelected = isCellSelected
}
}
Note we had to toggle the flag and update the cell and the model based on the new value.
Even better, you can replace the .accessory type updating line with:
tableView.reloadRows(at: [indexPath], with: .none)
So the method would look like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if let cell = tableView.cellForRow(at: indexPath) {
let isCellSelected = !(model[indexPath.row].isCellSelected)
model[indexPath.row].isCellSelected = isCellSelected
tableView.reloadRows(at: [indexPath], with: .none)
}
}
Note that we update the model, then we reload the cell that will cause cellForRowAt to be called, and that method takes care on proper configurations based on model.
More generally, I'd recommend to use some StateController object to keep the state for each cell / row and take care of the toggling.
I've wrote a whole article a while ago that does exactly what you need to do, and showcases a lot of best practices too:
https://idevtv.com/how-to-display-a-list-of-items-in-ios-using-table-views/
#Whirlwind, Hi its not so complicate if you need to show selections only inside the tableView, here i am delivering my ans after your model update.
This can be done by maintaining isCellSelected property in didSelectRowAt only.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
model[indexPath.row].isCellSelected = model[indexPath.row].isCellSelected ? false : true
tableView.reloadData()
}
Here is cellForRowAt you can also alter some more lines here.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let data = model[indexPath.row]
let identifier = "kNormalID"
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
cell.textLabel?.text = data.title
cell.detailTextLabel?.text = data.subtitle
cell.accessoryType = data.isCellSelected ? .checkmark : .none
return cell
}
Hope i delivered you my best. Let me know if i missed anything.
Why don't you try following!
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .none
model[indexPath.row].isCellSelected = false
tableView.reloadRows(at: [indexPath], with: <UITableViewRowAnimation>)
}
}
I have a tableView that when selected changes an image from one to another. This all works fine but when I select a tableCell it changes the image, but when I scroll it has also changed the image of another cell that I didn't select.
Below is my code.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FeaturesCell") as! FeaturesCell
cell.featuresLabel.text = self.items[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
pickedFeatures.append(items[indexPath.row])
let cell = tableView.cellForRow(at: indexPath) as! FeaturesCell
cell.checkImage.image = #imageLiteral(resourceName: "tick-inside-circle")
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
pickedFeatures.remove(at: pickedFeatures.index(of: items[indexPath.row])!)
let cell = tableView.cellForRow(at: indexPath) as! FeaturesCell
cell.checkImage.image = #imageLiteral(resourceName: "No-tick-inside-circle")
}
If I use detqueureusable cell in the did select function then it just doesn't change the picture at all when selected.
You can use tableView.dequeueReusableCell(_), The problem is, you didn't maintain the status of the selected cells.
Example :
class viewController: UIVieWController, UITableViewDelegate, UITableViewDataSource {
var selectedCellList = [IndexPath]()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FeaturesCell") as! FeaturesCell
cell.featuresLabel.text = self.items[indexPath.row]
if let _ = selectedCellList.index(of: indexPath) {
// Cell selected, update check box image with tick mark
cell.checkImage.image = #imageLiteral(resourceName: "tick-inside-circle")
} else {
// Cell note selected, update check box image without tick mark
cell.checkImage.image = #imageLiteral(resourceName: "No-tick-inside-circle")
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
pickedFeatures.append(items[indexPath.row])
if let index = selectedCellList.index(of: indexPath) {
selectedCellList.remove(at: index)
} else {
selectedCellList.append(indexPath)
}
tableView .reloadRows(at: [indexPath], with: .automatic)
}
}
how to return each element of loop. I mean i want display each name of this loop to row text. It is only return last element. How can i return all of this?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellFullname = tableview.dequeueReusableCell(withIdentifier: "cell", for: indexPath as IndexPath)
for last in lastnames {
cellFullname.textLabel?.text = "\(last)"
}
return cellFullname
}
You don't need a loop for displaying the elements in UITableView
Say you have an array of last names:
var lastnames = ["....
And you want to put each element in a UITableViewCell. Two steps:
Define the amount of cells you need:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return lastnames.count
}
Update the UITableView with the names:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellFullname = tableview.dequeueReusableCell(withIdentifier: "cell", for: indexPath as IndexPath)
cellFullname.textLabel?.text = lastnames[indexPath.row]
return cellFullname
}
Just change the part where you're assigning the textLabel.text :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellFullname = tableview.dequeueReusableCell(withIdentifier: "cell", for: indexPath as IndexPath)
cellFullname.textLabel?.text = lastnames[indexPath.row]
return cellFullname
}
You better create an array of elements arr[].
Use this :
cellFullname.textLabel?.text = arr[index.row]
Updated:
Just access each element of lastnames using indexPath.row.
let cellFullname = tableview.dequeueReusableCell(withIdentifier: "cell", for: indexPath as IndexPath)
cellFullname.textLabel?.text = lastnames[indexPath.row]
return cellFullname
}
If you will add for loop with in cellForRowAt then for each cell creation that loop will run, which not good as well.
In my tableView I can toggle between two cell classes depending on layout. So now I wonder how I can choose what cell class to select in the didEndDisplaying function.
Should I choose cell by using dequeueReusableCell like in the function bellow?
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if isBigCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellBig") as! BigTableViewCell
cell.myImageView.kf.cancelDownloadTask()
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellSmall") as! SmallTableViewCell
cell.myImageView.kf.cancelDownloadTask()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if isBigCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellBig") as! BigTableViewCell
let data = ads[(indexPath as NSIndexPath).row]
cell.configureWithData(data)
//Dont show highlight
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellSmall") as! SmallTableViewCell
let data = ads[(indexPath as NSIndexPath).row]
cell.configureWithData(data)
//Dont show highlight
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
}
}
The cell is given to you in the didEndDisplaying method. You determine its real type based on the indexPath, just like you did in the cellForRowAt method.
Do not dequeue another cell in didEndDisplaying. Just cast the provided cell based on the indexPath.
Since it seems that you don't use indexPath to determine then cell type, then your code should be something like this:
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if isBigCell {
if let bigcell = cell as? BigTableViewCell {
bigcell.myImageView.kf.cancelDownloadTask()
}
} else {
if let smallcell = cell as? SmallTableViewCell {
smallcell.myImageView.kf.cancelDownloadTask()
}
}
}