I need to get textLabel from row in UITableView. But I get nil. I need to get textLabel, because I'm using UISearchBar, if I will try to get data using index, when searching, I will receive incorrect indexes. So , I want to get textLabel.
Please, fix where I'm wrong
When I am typing in searchBar items in TableView change. But index doesn't. For example Food = ["Apple", "Banana", "Coca-Cola"]. If I use searchBar and enter "Banana". Then I click on this , but I get Apple (because this is index - 0). That's why I want to get textLabel
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//let meal = foods[indexPath.item]
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let labelContent = cell.textLabel!.text
print(labelContent)
}
UISearchBar
extension FoodViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchedFoods = foods.filter({ $0.title.lowercased().prefix(searchText.count) == searchText.lowercased() })
searching = true
tableView.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searching = false
searchBar.text = ""
tableView.reloadData()
}
}
Never get data from the view, the cell, get it always from the model, the data source.
And never use dequeueReusableCell outside of cellForRowAt. You won't get the cell you expect.
In this case you have to get the data depending on searching
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let labelContent : String
if searching {
labelContent = searchedFoods[indexPath.row].title
} else {
labelContent = foods[indexPath.row].title
}
print(labelContent)
}
And your filter method is horrible. Change it to much more efficient
searchedFoods = foods.filter { $0.title.range(of: searchText, options: [.caseInsensitive, .anchored] != nil) }
Finally I recommend to change textDidChange to cancel searching also if the search text becomes empty and remove the unused items from searchedFoods.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
searchedFoods.removeAll()
searching = false
} else {
searchedFoods = foods.filter{ $0.title.range(of: searchText, options: [.caseInsensitive, .anchored]) != nil }
searching = true
}
tableView.reloadData()
}
As I have figured out the problem is that you are using indexPath.item that's why you are getting the wrong index please try this
let meal = foods[indexPath.row]
If you want to get a text from the UILabel on tap of cell
confirm UITableViewDelegate
//Access the array that you have used to fill the tableViewCell
print(foods[indexPath.row]) it will print selected row
OR
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! UITableViewCell
let labelContent = cell.textLabel!.text
print(labelContent)
}
Lets just say instead of getting data from foods (in didSelect method) get it from searched Food. But for that to work properly you should assign searched food with food initially and also when the user clears search bar you should again reset searched food to food data.
Please try like this,
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if searching {
print(searchedFoods[indexPath.row])
} else {
print(foods[indexPath.row])
}
}
Try This
extension FoodViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if searching {
let food = searchedFoods[indexPath.row]
cell.textLabel?.text = food.title
} else {
let food = foods[indexPath.row]
cell.textLabel?.text = food.title
}
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return searchedFoods.count
} else {
return foods.count
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if searching {
let meal = searchedFoods[indexPath.row]
let labelContent = meal
print(labelContent)
} else {
let meal = foods[indexPath.row]
let labelContent = meal
print(labelContent)
}
}
Related
I am trying to grab the information from cell supplied while searching and bring it back to prior view controller. Here is my code:
//variables:
var searching = false
var cityArray = ["New York City, NY", "LA, CA"]
var searchCity = [String()]
//setup
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return searchCity.count
} else {
return cityArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CityTableViewCell", for: indexPath) as!
CityTableViewCell
if searching {
cell.cityLabel.text = searchCity[indexPath.row]
} else {
cell.cityLabel.text = cityArray[indexPath.row]
}
return cell
}
//tells what do once selecting the tableviewcell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if searching {
//This is the problem. I am telling to take the search city IndexPath.row
currentCity = searchCity[indexPath.row]
} else {
currentCity = cityArray[indexPath.row]
}
//currentCity is then used to go back to prev. VC and tell it what city was selected
if let presentingVC = presentingViewController as? DealsViewController {
if presentingVC.city != self.currentCity {
presentingVC.city = self.currentCity
presentingVC.deleteAllDeals()
presentingVC.noDeals = false
presentingVC.cityOutlet.setTitle(self.currentCity,for: .normal)
DispatchQueue.main.async {
presentingVC.getDealInfo {
presentingVC.DealsTableView.reloadData()
presentingVC.DealsTableView.layoutIfNeeded()
}
}
}
}
searchBar.text = currentCity
searching = false
tableView.deselectRow(at: indexPath, animated: true)
self.dismiss(animated: true, completion: nil)
searchBar.resignFirstResponder()
}
//searchBar
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchCity = cityArray.filter({$0.prefix(searchText.count) == searchText})
searching = true
reloadData()
}
Unfortunately, if it is in searching mode, the city returned does not change. It is the same as in previous view controller. I am looking to grab the exact indexpath.row from searchCity.
Thanks!
Figured this out:
There was a transcription error
to change it to lowercase:
searchCity = cityArray.filter({$0.lowercased().prefix(searchText.count) ==
searchText.lowercased()})
Thanks!
It's easier to explain by example. I have original array which is searched and filtered array with searched items. If i found one item after searching and tap on it, i mark it as done (I have todo list), but when i cancel my search, I find that the first element in the original array is marked, not the third item.
I googled some threads and found almost similar problems, but solutions doesn't suit to my problem. For example:
didSelectRowAtIndexPath indexpath after filter UISearchController - Swift
And here some code. Especially at didSelectRowAt I mark the items to done. Does anyone have any ideas?
private var searchBarIsEmpty: Bool {
guard let text = searchController.searchBar.text else { return false }
return text.isEmpty
}
private var isFiltering: Bool {
return searchController.isActive && !searchBarIsEmpty
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering {
return filteredTasks?.count ?? 0
}
return manager.tasks.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Keys.cell.rawValue, for: indexPath) as! ToDoCell
var currentItem: Task
if isFiltering {
currentItem = filteredTasks?[indexPath.row] ?? manager.tasks[indexPath.row]
} else {
currentItem = manager.tasks[indexPath.row]
}
cell.titleLabel.text = currentItem.taskName
cell.descriptionLabel.text = currentItem.description
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let accessoryType: UITableViewCell.AccessoryType = manager.changeState(at: indexPath.row) ? .checkmark : .none
tableView.cellForRow(at: indexPath)?.accessoryType = accessoryType
}
When you use tableView.dequeueReusableCell, you may get the old cell, so you should update it. You should read doc.
#PGDev already said in comments that you should save checked/unchecked status in your model.
I hope my example will help you.
You can contain state of cells in cell models:
class YourCellModel {
var task: Task
var checked: Bool
init(task: Task, checked: Bool) {
self.task = task
self.checked = checked
}
}
And add it in ToDoCell:
//...
var model: YourCellModel {
didSet {
updateViews()
}
}
func updateViews() {
titleLabel.text = task.taskName
descriptionLabel.text = task.description
if model.checked {
//...
} else {
//....
}
}
And update model here:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Keys.cell.rawValue, for: indexPath) as! ToDoCell
// You should contain cell models to remember their states
let model = cellModels[indexPath.row]
cell.model = model
return cell
}
When the user checks a cell, you should save it in your cell model. You can do it in ToDoCell:
func checked() {
model.checked = true
}
Note: If isFiltering is true, it is a different array of cell models.
UPD. I noticed your Task is similar to a cell model. You can save checked status there. But your cell should have access to it.
So far Ive gotten my searchbar to work with my firebase Firestore App, but for some reason my searchbar is not retrieving data accurately. Because, no matter what text I type in the search bar, the tableview presents the same item as my search results.
Im using Cloud Firestore as my database, but I don't think that that's the issue for why my search is posting the same item as the search result.
extension ProductListController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchBar.text != "" {
return self.productInventory.count
}
return productSetup.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ProductListCell") as?
ProductListCell else { return UITableViewCell() }
//search not empty
if searchBar.text != "" {
cell.configure(withProduct: productInventory[indexPath.row])
}
cell.configure(withProduct: productSetup[indexPath.row])
return cell
}
}
I've looked through multiple questions on stack and seems like the answers I'm getting are not much help. Hopefully someone here can help me figure this out and solve my issue.
I think problem is in cellForRowAt:- Try This
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ProductListCell") as?
ProductListCell else { return UITableViewCell() }
//search not empty
if searchBar.text != "" {
cell.configure(withProduct: productInventory[indexPath.row])
}else{
cell.configure(withProduct: productSetup[indexPath.row])
}
return cell
}
Im using a tableview to display an array of strings. When I click on a particular row, I want it to be highlighted with a checkmark. When I deselect a row, I want the checkmark to be removed. When I press a button, I want the rows that are currently highlighted to be passed out in an array(newFruitList).
My problem is that when I click the first row, the last is highlighted. When I uncheck the first row, the last is unchecked, as if they are the same cell?
How do I overcome this?
Also, the way I am adding and removing from my new array, is this the correct way to go about doing this?
Thanks
My Code:
class BookingViewController: UIViewController, ARSKViewDelegate, UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var table: UITableView!
let fruits = ["Apples", "Oranges", "Grapes", "Watermelon", "Peaches"]
var newFruitList:[String] = []
override func viewDidLoad() {
super.viewDidLoad()
self.table.dataSource = self
self.table.delegate = self
self.table.allowsMultipleSelection = true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fruits.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = table.dequeueReusableCell(withIdentifier: "Cell")
if cell == nil{
cell = UITableViewCell(style: .subtitle, reuseIdentifier: "Cell")
}
cell?.textLabel?.text = fruits[indexPath.row]
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
newFruitList.append(fruits[indexPath.row])
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .checkmark
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if let index = newFruitList.index(of: fruits[indexPath.row]) {
newFruitList.remove(at: index)
}
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .none
}
}
#IBAction func bookButtonPressed(_ sender: UIButton) {
//testing purposes
for i in stride(from: 0, to: newFruitList.count, by: 1){
print(newFruitList[i])
}
}
They are probably the same cell because you use dequeueReusableCell and it reuses old cells.
use:
override func prepareForReuse() {
super.prepareForReuse()
}
To reset the cell and it should be fine.
As for the save and send mission. Create an pre-indexed array that you can populate.
var selected: [Bool] = []
var fruits: [Fruit] = [] {
didSet {
selected = Array(repeating: false, count: fruits.count)
}
}
And in your didSelectItemAt you do:
selected[indexPath.item] = !selected[indexPath.item]
UITableView reuses the cell that is already present and hence you will see that duplicate check mark, so to solve this issue you need to clear the cell states while loading cell. for that you can create a model with property to track the states of your selected cells
So your fruit model must be like below
class Fruit{
var name:String
var isSelected:Bool
init(name:String){
isSelected = false
self.name = name
}
}
Then you will have table view populated with Fruit list
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = table.dequeueReusableCell(withIdentifier: "Cell")
if cell == nil{
cell = UITableViewCell(style: .subtitle, reuseIdentifier: "Cell")
}
let model = fruits[indexPath.row]
cell?.textLabel?.text = model.name
if(model.isSelected){
cell.accessoryType = .checkmark
}
else{
cell.accessoryType = .none
}
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
newFruitList.append(fruits[indexPath.row])
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .checkmark
var model = fruits[indexPath.row]
model.isSelected = true
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if let index = newFruitList.index(of: fruits[indexPath.row]) {
newFruitList.remove(at: index)
}
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .none
var model = fruits[indexPath.row]
model.isSelected = false
}
}
below code supposed to call usernames from an array and user able to tap on the desired username. call to that array is a success and I can see the usernames but won't able to tap into it. and it don't even print "didSelectRowAt". appreciate your help. thanks.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier", for: indexPath) as! MentionUserCell
cell.username!.text = autoComplete[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return autoComplete.count
}
func numberOfSections(in tableView: UITableView) -> Int
{
return 1
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let selectedCell: MentionUserCell = tableView.cellForRow(at: indexPath)! as! MentionUserCell
let selectedWord: String = selectedCell.username!.text! + " "
print("didSelectRowAt")
var string: NSString = NSString(string: self.commentTextView.text!)
string = string.replacingCharacters(in: otoCadang.sharedInstance.foundRange, with: selectedWord) as NSString
// change text field value
commentTextView.text = string as? String
// change cursor position
let positionOriginal = commentTextView.beginningOfDocument
if let cursorLocation: UITextPosition = self.commentTextView.position(from: positionOriginal, offset: otoCadang.sharedInstance.foundRange.location + selectedWord.characters.count)
{
self.commentTextView.selectedTextRange = self.commentTextView.textRange(from: cursorLocation, to: cursorLocation)
}
// remove suggestion
self.autoComplete.removeAll()
self.tableView.reloadData()
tableView.isHidden = true
}
Check List
tableview delegate is set properly
tableview selection mode == No Selection, then change to single selection
function name is correct func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
are you using any tap gesture on this view? remove it.
If you are editing UITextView will can't call to `didselectRowAt. Try to get the Your MentionUserCell, IndexPath in UITextView's methods and handle there. Example:
func textViewDidChange(_ textView: UITextView) {
var parentView = textView as UIView
while !(parentView is UITableViewCell) {
parentView = parentView.superview!
}
if let cell: MentionUserCell = parentView as? MentionUserCell {
if let indexPath = self.tableView.indexPath(for: cell) {
let yourIndexPath = indexPath
}
}
//your code
}
Hope it will help you.
When are using two or more cells the didSelectRow method is not working Well. You can use a button in cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifierName", for: indexPath) as! MentionUserCell
cell.username!.text = autoComplete[indexPath.row]
let button = cell.viewWithTag(101) as! UIButton
button.cellclick.addTarget(self, action: #selector(functionName(sender:)), for: .touchDown)
return cell
}
Now you get the indexPath:-
func functionName(sender:UIButton) {
let buttonPosition:CGPoint = sender.convert(CGPoint.zero, to:self.tableview)
let indexPath = self.tableview.indexPathForRow(at: buttonPosition)
print(indexPath.row)
}