The problem is fairly weird, I try to fetch some data and then I reload data after getting them. I type in searchbar the first letter and it doesn't reload the data in UITableview but when the searchText gets edited the next time the UITableView gets reloaded from itself immediately.
I looked at many solutions with no gain.
Both delegates are set in Storyboard.
Here is my code:
extension SearchVC: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
reload(searchText) { (users) in
self.queriedUsers = users
DispatchQueue.main.async {
print("Table view reloading \(self.queriedUsers.count)")
self.tableview.reloadData()
}
}
}
}
extension SearchVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableview.dequeueReusableCell(withIdentifier: queriedCellID, for: indexPath) as! QueriedUserViewCell
cell.nicknameLabel.text = queriedUsers[indexPath.row].getNickname()
cell.usernameLabel.text = queriedUsers[indexPath.row].getName()
cell.userProfilePicture.image = UIImage(named: "defaultpfp")
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return queriedUsers.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70.0
}
}
Related
This question already has an answer here:
How to make search results are not showing in my table view from my search bar?
(1 answer)
Closed last year.
I have a table view with cells. Here is how it looks like:
The main question is how it possible to make, that when user write "Test", so it should show all what match for this word. Here is some method, which I've tried to use, but it's not working:
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchedArray = activeStates.filter({ titleClass -> Bool in
titleClass.title!.contains(searchText)
})
searching = true
tableView.reloadData()
}
Now, I will show all data which I have. var activeStates: [ActiveState] = []. Here is my tableView methods:
extension HomeViewController: UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 118
}
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let classCell = cell as! ClassTableViewCell
classCell.sd_cancelCurrentImageLoad()
classCell.imgClass.image = nil
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let classCell = cell as! ClassTableViewCell
let asCell = self.activeStates[indexPath.row]
classCell.lblTitle.text = asCell.title
if let imageUrl: String = asCell.thumbnailUrl {
if imageUrl != "" {
classCell.imgClass.sd_setImage(with: URL(string: imageUrl), placeholderImage: placeholderImage)
}
} else {
classCell.imgClass.image = UIImage(named: "classImagePlaceholder")
}
classCell.unreadClassImage.isHidden = unreadManager.unreadTable[asCell.idKey] == nil
classCell.lblTitle.font = unreadManager.unreadTable[asCell.idKey] == nil ? Constants.CustomFont.customFontSemiBold : Constants.CustomFont.customFontBold
classCell.classId = asCell.idKey
classCell.selectionStyle = .none
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
store.dispatch(NavigationAction(destination: .paymentScreen, direction: .forward))
print("SelecterRow")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return activeStates.count
}
//swiftlint:disable force_cast
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
switch tableView {
case tableView:
cell = tableView.dequeueReusableCell(withIdentifier: "classCell", for: indexPath)
case secondTableView:
cell = tableView.dequeueReusableCell(withIdentifier: "classCell", for: indexPath)
default:
print("Something goes wrong")
}
// let cell = tableView.dequeueReusableCell(withIdentifier: "classCell", for: indexPath) as! ClassTableViewCell
return cell
}
I think, maybe need to filter titleName of the cell or something else.
when you are in "searching state" you should get your data from the filtered array (searchedArray) and not from the original activeState array, in your case try something like this:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let classCell = cell as! ClassTableViewCell
let asCell = searching ? self.searchedArray[indexPath.row] :self.activeStates[indexPath.row]
classCell.lblTitle.text = asCell.title
if let imageUrl: String = asCell.thumbnailUrl {
if imageUrl != "" {
classCell.imgClass.sd_setImage(with: URL(string: imageUrl), placeholderImage: placeholderImage)
}
} else {
classCell.imgClass.image = UIImage(named: "classImagePlaceholder")
}
classCell.unreadClassImage.isHidden = unreadManager.unreadTable[asCell.idKey] == nil
classCell.lblTitle.font = unreadManager.unreadTable[asCell.idKey] == nil ? Constants.CustomFont.customFontSemiBold : Constants.CustomFont.customFontBold
classCell.classId = asCell.idKey
classCell.selectionStyle = .none
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
store.dispatch(NavigationAction(destination: .paymentScreen, direction: .forward))
print("SelecterRow")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return searchedArray.count
}
return activeStates.count
}
EDIT:
set the searching variable using the UISearchBarDelegate:
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
searching = true
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
searching = false
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchedArray = activeStates.filter({ titleClass -> Bool in
titleClass.title!.contains(searchText)
})
tableView.reloadData()
}
I have a tableView displaying [Double]. Very Simple. But I also wanna display the average of the onscreen numbers on every row, and the difference between this number and the average.
Because I need to re-calculated the average every time a new row appears, I'm thinking about accessing tableView.visibleCells in cellForRowAt: indexPath method, and then update the average of this row and every other rows on screen, because the average of onscreen rows should be the same for all the onscreen rows.
But then I got this error message [Assert] Attempted to access the table view's visibleCells while they were in the process of being updated, which is not allowed. Make a symbolic breakpoint at UITableViewAlertForVisibleCellsAccessDuringUpdate to catch this in the debugger and see what caused this to occur. Perhaps you are trying to ask the table view for the visible cells from inside a table view callback about a specific row?
While this is loud and clear, I'm wondering what is the correct way or workaround for this?
Code is very simple
class ViewController: UIViewController {
var data:[Double] = [13,32,43,56,89,42,26,17,63,41,73,54,26,87,64,33,26,51,99,85,57,43,30,33,20]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.dataSource = self
self.tableView.delegate = self
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "default")!
cell.textLabel?.text = "\(data[indexPath.row])"
print(tableView.visibleCells.count) // THIS LINE PRODUCE ERROR
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 64
}
}
What I have tried:
I've tried didEndDisplaying and willDisplay, when I added print(tableView.visibleCells.count) to either of them, same error message was given back.
Answer:
You can use UITableView's delegate functions to calculate this.
tableView(_:willDisplay:forRowAt:) is called every time before cell becomes visible, so you can recalculate your average value at this moment. Also, there is tableView(_:didEndDisplaying:forRowAt:) which fires when cell goes off display and also can be used to recalculate.
Documentation:
tableView(_:willDisplay:forRowAt:)
tableView(_:didEndDisplaying:forRowAt:)
UPD:
For calculation use tableView.indexPathsForVisibleItems
Example:
import UIKit
class ViewController: UIViewController {
var data:[Double] = [13,32,43,56,89,42,26,17,63,41,73,54,26,87,64,33,26,51,99,85,57,43,30,33,20]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
self.tableView.dataSource = self
self.tableView.delegate = self
self.tableView.reloadData()
}
private func calculate() {
let count = tableView.indexPathsForVisibleRows?.count
let sum = tableView.indexPathsForVisibleRows?
.map { data[$0.row] }
.reduce(0) { $0 + $1 }
if let count = count, let sum = sum {
print(sum / Double(count))
}
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "default")!
cell.textLabel?.text = "\(data[indexPath.row])"
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 64
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
calculate()
}
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
calculate()
}
}
I have a table view with two sections and want to allow rows to be re-ordered for just one of the sections. I've found a lot of information about this, but can't restrict it to the single section. Here is my entire code - it's very simple, with one table view. I've put this together from various sources - thanks to those who have contributed to this topic.
To reproduce this in Storyboard, add a table view to the view controller, add some constraints, set number of Prototype Cells to 1, and set its Identifier to 'cell'.
The table view has two sections - 'Fruit' and 'Flowers'. Press and hold on any cell allows that cell to be moved, so this part works ok.
I want to restrict it so that I can only move in the first section.
Also, if I'm dragging a cell and move it from one section to another, it gives an error and the program crashes. I'd like it just to reject the move and send the cell back to its original position.
Thanks for any help. Ian
import UIKit
import MobileCoreServices
var fruitList = ["Orange", "Banana", "Apple", "Blueberry", "Mango"]
var flowerList = ["Rose", "Dahlia", "Hydrangea"]
// this all works
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITableViewDragDelegate, UITableViewDropDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dragInteractionEnabled = true
tableView.dragDelegate = self
tableView.dropDelegate = self
tableView.dragInteractionEnabled = true
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "Fruit"
} else {
return "Flowers"
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return fruitList.count
} else {
return flowerList.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if indexPath.section == 0 {
cell.textLabel?.text = fruitList[indexPath.row]
} else {
cell.textLabel?.text = flowerList[indexPath.row]
}
return cell
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
}
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
var string: String
if indexPath.section == 0 {
string = fruitList[indexPath.row]
} else {
string = flowerList[indexPath.row]
}
guard let data = string.data(using: .utf8) else { return [] }
let itemProvider = NSItemProvider(item: data as NSData, typeIdentifier: kUTTypePlainText as String)
return [UIDragItem(itemProvider: itemProvider)]
}
func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
}
}
Just in case anyone finds their way here, thanks to the link #Paulw11 has provided, this is the extra bit of code needed. Replace
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
}
with
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let string = fruitList[sourceIndexPath.row]
fruitList.remove(at: sourceIndexPath.row)
fruitList.insert(string, at: destinationIndexPath.row)
}
Obviously this is just a simple example program, but I've now adapted this code to a complex situation and it works perfectly.
A bit of context:
I'm filling a tableView with movie titles
my table
When one of those rows gets selected I want to go to the movie detail
but when I tap any of the rows nothing happens.
class SearchTableViewController: NavigationController{
#IBOutlet weak var tableView: UITableView!
var filteredMovies = [Movie]()
let request = Requests()
override func viewDidLoad() {
super.viewDidLoad()
}
/*
searches movies from the db which title corresponds to the given text
*/
func searchMovie(_ keywords: String) {
request.searchMovie(keywords: keywords){ response in
for data in response{
self.filteredMovies.append(data)
}
DispatchQueue.main.sync {
self.tableView?.reloadData()
}
}
}
}
extension SearchTableViewController : UITableViewDelegate { }
extension SearchTableViewController: UISearchBarDelegate {
/*
every time a key gets pressed, the table view gets updateted
with new movie titles
*/
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.filteredMovies = [Movie]()
searchText.isEmpty ? self.tableView?.reloadData() : searchMovie(searchText)
}
}
extension SearchTableViewController : UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredMovies.count
}
func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
print("hello from highlight")
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("hello from selection")
}
func tableView(_ tableView: UITableView, didFocusRowAt indexPath: IndexPath) {
print("hello from focus")
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! SearchTableCell
cell.onDidSelectMovie = {
super.goToMovieDetail(movie: self.filteredMovies[indexPath.row])
}
let label = cell.contentView.subviews.first as! UILabel
label.text = filteredMovies[indexPath.row].title
return cell
}
}
As you can see i tested every function that may show that a row got tapped but none of them works.
I also tried to give to the cells a custom class where I override the behavior of the cell property "isSelected".
class SearchTableCell: UITableViewCell {
var onDidSelectMovie:(()->Void)?
override var isSelected: Bool{
didSet {
if isSelected{
if let cb = onDidSelectMovie{
cb()
}
}
}
}
}
Another thing that I have want to point out, which may help, is that "selection" on a cell is enabled like "user interactions" but if I try to change selection from "default" to "blue" the color doesn't change.
I literally ran out of ideas and tried many possible solutions but none of them worked. Any suggestion?
Edit:
I'm going to add everything that can be useful
the tableView delegate is SearchTableViewController
It looks like I had just to remove and add again the tableView delegate... don't ask me why, I've spent 2 days on this. Thanks anyway to everyone who tried to help.
The table cells contained in the first view are properly represented, but the table cells contained in the second view do not appear. I can’t understand why.
HelpController.swift
import UIKit
class HelpController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// MARK: - Initialize
#IBOutlet weak var menuTable: UITableView!
let helpMenu = ["a","b","c"]
override func viewDidLoad() {
super.viewDidLoad()
//datasource link
menuTable.delegate = self
menuTable.dataSource = self
self.menuTable?.tableFooterView = UIView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table View Data Source
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return helpMenu.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "HelpMenuCell")
cell.textLabel?.text = helpMenu[indexPath.row]
return cell
}
}
When i check it with print (helpmenu.count), it return 3. It seems to work well until numberOfRowsInSection, but cellForRowAt does not work.
and this is my first view InfosController.swift
// MARK: - Table View Data Source
// get cell count
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if UserDefaults.standard.value(forKey: "userid") != nil {
// sign in state
return memberMenu.count
} else {
// sign out state
return nonMemberMenu.count
}
}
// change cell text
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "InfosMenuCell")
if UserDefaults.standard.value(forKey: "userid") != nil {
// sign in state
cell.textLabel?.text = memberMenu[indexPath.row]
} else {
// sign out state
cell.textLabel?.text = nonMemberMenu[indexPath.row]
}
return cell
}
This is the code included in the first view that works properly. This works well, but I’m confused because it does not work well in the second view(HelpController).
ps.
The problem in HelpController, In controlle self.menuTable?.tableFooterView = UIView()
So if You are having footer view then You need to write the delegate function heightForFooterInSection and viewForFooterInSection
func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
}
func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
}
Make sure below things
Make sure you added Delegate & DataSource to tableView.
Check your helpMenu count is not equal to
Zero.
If numberOfRowsInSection returns Zero, then probably cellForRowAt
indexPath won't call.
Please check the method declaration once the correct syntax is
func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell { }