I am creating a custom tableViewController with searchBar. I created a custom cell class "DataCell".
With following code searchBar is not shown and array of label is not displayed.
How can I solve this problem?
import UIKit
class DataCell: UITableViewCell{
#IBOutlet weak var label: UILabel!
}
class SearchController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
var isSearching = false
var data = ["a","b","c","d","e"]
var filterData = [String]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
searchBar.delegate = self
searchBar.returnKeyType = UIReturnKeyType.done
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isSearching{
return filterData.count
}
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "DataCell", for: indexPath) as? DataCell {
let text: String!
if isSearching {
text = filterData[indexPath.row]
} else {
text = data[indexPath.row]
}
return cell
} else {
return UITableViewCell()
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == ""{
isSearching = false
view.endEditing(true)
tableView.reloadData()
}else {
isSearching = true
filterData = data.filter({$0 == searchBar.text})
tableView.reloadData()
}
}
}
As of iOS 8, you should be using the [UISearchController][1] for which you need to create 2 classes. A SearchController and a ResultsController. We start by creating a common UITableView class:
import UIKit
class DataCell: UITableViewCell {
#IBOutlet weak var label: UILabel!
func configureCell(_ text: String) {
label.text = text
}
}
Then, for the search class:
class SearchController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var searchController: UISearchController!
var resultController: ResultController?
var data = ["a","b","c","d","e"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(TableCell.self, forCellReuseIdentifier: "DataCell")
tableView.register(UINib(nibName: "DataCell", bundle: Bundle.main), forCellReuseIdentifier: "DataCell")
// Search Results
resultController = ResultController()
setupSearchControllerWith(resultController!)
if #available(iOS 9.0, *) {
searchController?.loadViewIfNeeded()
}
tableView.delegate = self
tableView.dataSource = self
resultController.tableView.delegate = self
}
}
extension: SearchController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "DataCell", for: indexPath) as? DataCell {
return cell.configureCell(data[indexPath.row])
} else {
return UITableViewCell()
}
}
}
extension SearchController: UITableViewDelegate {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView == resultController?.tableView {
performSegue(withIdentifier: "DetailView", sender: resultController?.filterData[indexPath.row])
} else {
performSegue(withIdentifier: "DetailView", sender: data[indexPath.row])
}
}
}
extension SearchController: UISearchResultsUpdating,
UISearchControllerDelegate,
UISearchBarDelegate {
func updateSearchResults(for searchController: UISearchController) {
let resultsTable = searchController.searchResultsController as! ResultVC
// resultsTable.query = searchController.searchBar.text!
resultsTable.filterData = data.filter({
$0 == searchController.searchBar.text!
})
resultsTable.tableView.reloadData()
}
func setupSearchControllerWith(_ results: ResultVC) {
// Register Cells
results.tableView.register(TableCell.self, forCellReuseIdentifier: "DataCell")
results.tableView.register(UINib(nibName: "DataCell", bundle: Bundle.main), forCellReuseIdentifier: "DataCell")
// We want to be the delegate for our filtered table so didSelectRowAtIndexPath(_:) is called for both tables.
results.tableView.delegate = self
searchController = UISearchController(searchResultsController: results)
// Set Search Bar
searchController.searchResultsUpdater = self
searchController.searchBar.sizeToFit()
tableView.tableHeaderView = searchController.searchBar
// Set delegates
searchController.delegate = self
searchController.searchBar.delegate = self
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == ""{
isSearching = false
view.endEditing(true)
tableView.reloadData()
} else {
isSearching = true
filterData = data.filter({$0 == searchBar.text})
tableView.reloadData()
}
}
}
Then, for the ResultsController class:
class ResultController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var filterData = [String]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(TableCell.self, forCellReuseIdentifier: "DataCell")
tableView.register(UINib(nibName: "DataCell", bundle: Bundle.main), forCellReuseIdentifier: "DataCell")
tableView.dataSource = self
}
}
extension ResultController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let rowCount = filterData.count
// When no data insert centered label
if rowCount == 0 {
blankView(with: textForEmptyLabel)
} else {
// Remove empty table label
tableView.backgroundView = nil
tableView.separatorStyle = .singleLine
}
return rowCount
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "DataCell", for: indexPath) as? DataCell {
return cell.configureCell(filterData[indexPath.row])
} else {
return UITableViewCell()
}
}
}
Related
I have a Table View that is working fine. However, when I try to implement a UISearchBar and display filtered data, nothing gets filtered. This is my View Controller:
import UIKit
class MealPlanViewController: UIViewController, UISearchBarDelegate {
private var model = MealPlanModel()
private var mealPlan = [MealPlan]()
var filteredData: [MealPlan]!
#IBOutlet weak var topBarStackView: UIStackView!
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
model.delegate = self
searchBar.delegate = self
filteredData = mealPlan
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredData = []
if searchText == "" {
filteredData = mealPlan
}
else {
for item in mealPlan {
if ((item.title?.lowercased().contains(searchText.lowercased())) != nil) {
filteredData.append(item)
}
}
}
self.tableView.reloadData()
}
}
extension MealPlanViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MealPlanCell", for: indexPath) as! MealPlanCell
let filteredMealPlaninTable = filteredData[indexPath.row]
cell.displayMealPlan(filteredMealPlaninTable)
return cell
}
}
extension MealPlanViewController: MealPlanProtocol {
func mealPlansRetrieved(mealPlans: [MealPlan]) {
self.filteredData = mealPlans
tableView.reloadData()
}
}
A couple of notes:
When I run a print(self.filteredData) in my `func mealPlansRetrieved', the console returns all of my data as if it wasn't filtered, but
As soon as I start typing in the search bar, the table view doesn't return any cells, which seems contradictory to the above
For reference, this is the code before filtering that did work:
extension MealPlanViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return mealPlan.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MealPlanCell", for: indexPath) as! MealPlanCell
let mealPlanInTable = mealPlan[indexPath.row]
cell.displayMealPlan(mealPlanInTable)
return cell
}
}
extension MealPlanViewController: MealPlanProtocol {
func mealPlansRetrieved(mealPlans: [MealPlan]) {
self.mealPlan = mealPlans
tableView.reloadData()
}
}
Any help/guidance is much appreciated!
Contains returns boolean so this will never fail: item.title?.lowercased().contains(searchText.lowercased())) != nil
To make check work you can simply remove "!= nil".
Im not sure from where you are calling mealPlansRetrieved, but you might want to keep the line "self.mealPlan = mealPlans" instead of "self.filteredData = mealPlans".
I have dynamic table which displays a number of MKMapItems - these items are being displayed by cellForRowAt. But somehow if I click on the cell didSelectRow won't return the map item if I want to print it:
class SearchResultTableViewController: UIViewController {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var visualEffectView: UIVisualEffectView!
private enum SegueID: String {
case showDetail
case showAll
}
private enum CellReuseID: String {
case resultCell
}
private var places: [MKMapItem]? {
didSet {
tableView.reloadData()
}
}
private var suggestionController: SuggestionsTableTableViewController!
var searchController: UISearchController!
private var localSearch: MKLocalSearch? {
willSet {
places = nil
localSearch?.cancel()
}
}
private var boundingRegion: MKCoordinateRegion?
override func awakeFromNib() {
super.awakeFromNib()
...
}
override func viewDidLoad() {
super.viewDidLoad()
...
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if #available(iOS 10, *) {
visualEffectView.layer.cornerRadius = 9.0
visualEffectView.clipsToBounds = true
}
}
...
}
extension SearchResultTableViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return places?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CellReuseID.resultCell.rawValue, for: indexPath)
if let mapItem = places?[indexPath.row] {
cell.textLabel?.text = mapItem.name
cell.detailTextLabel?.text = mapItem.placemark.formattedAddress
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
print(indexPath)
...
guard let mapItem = places?[indexPath.row] else { return }
print(mapItem.name)
...
if tableView == suggestionController.tableView, let suggestion = suggestionController.completerResults?[indexPath.row] {
searchController.isActive = false
searchController.searchBar.text = suggestion.title
search(for: suggestion)
}
}
}
guard let mapItem = places?[indexPath.row] else { return }
- are you sure this guard statement isn't returning?
also, I'm not seeing where you're setting the VC as the tableView delegate/datasource.
It is a question.
This class is not key value code-compliant for the key error is given.
However, I am not sure which part is wrong. This error appears at cellForRowAt.
Thank you for your answer.
If you want it, I can also show the code of ContentTableViewCell, so please call out.
import UIKit
class SearchViewController: UIViewController, UISearchResultsUpdating {
#IBOutlet weak var tableview: UITableView!
var songs = [
"裁量権を持って、若者向けカレンダーアプリ開発をしたいiOSエンジニア募集!",
"自信を持って、応援出来るエンジニアを募集!"
]
var searchSongs = [String]()
func updateSearchResults(for searchController: UISearchController) {
let searchString = searchController.searchBar.text!
searchSongs = songs.filter {(name) -> Bool in
return name.contains(searchString)
}
tableview.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.prefersLargeTitles = true
let searchController = UISearchController(searchResultsController:nil)
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
navigationItem.searchController = searchController
definesPresentationContext = true
delegate()
}
}
extension searchViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if navigationItem.searchController?.isActive == true {
return searchSongs.count
} else {
return songs.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableview.dequeueReusableCell(withIdentifier: "ContentTableViewCell", for: indexPath) as! ContentTableViewCell
if navigationItem.searchController?.isActive == true {
cell.commonInit(content: searchSongs[indexPath.row])
} else {
cell.commonInit(content: songs[indexPath.row])
}
return cell
}
func delegate() {
tableview.delegate = self
tableview.dataSource = self
let nibName = UINib(nibName: "ContentTableViewCell", bundle: nil)
self.tableview.register(nibName, forCellReuseIdentifier: "ContentTableViewCell")
}
}
Check in your Storyboard. I think any outlet is not connected properly.
Check is there any yellow warning triangle in your storyboard on which screen this error is coming. If you found any, remove and connect the outlet again.
I started a table view with a list of universities and created a search bar to tag along with it. The search bar works but only if I type in the name of the school exactly how it is. Is there a way I can change the it to search any part of the name and get the same results? Here's the code that I have set up.
#IBOutlet weak var schoolSearch: UISearchBar!
#IBOutlet weak var tblView: UITableView!
let schoolnames = ["Long Beach City College LAC", "California State University, Bakersfield", ...]
var searchedSchool = [String]()
var searching = false
override func viewDidLoad() {
super.viewDidLoad()
schoolSearch.delegate = self
self.tblView.delegate = self
self.tblView.reloadData()
}
extension ChooseSchool: UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return searchedSchool.count
} else {
return schoolnames.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableViewCell
cell?.img.image = UIImage(named: schoolnames[indexPath.row])
cell?.lbl.text = schoolnames[indexPath.row]
_ = tableView.dequeueReusableCell(withIdentifier: "cell")
if searching {
cell?.textLabel?.text = searchedSchool[indexPath.row]
} else {
cell?.textLabel?.text = schoolnames[indexPath.row]
}
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = storyboard?.instantiateViewController(withIdentifier: "TestController") as? TestController
vc?.schoolnames = schoolnames[indexPath.row]
navigationController?.pushViewController(vc!, animated: true)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchedSchool = schoolnames.filter({$0.lowercased().prefix(searchText.count) == searchText.lowercased()})
searching = true
tblView.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searching = false
searchBar.text = ""
tblView.reloadData()
}
}
Replace
searchedSchool = schoolnames.filter({$0.lowercased().prefix(searchText.count) == searchText.lowercased()})
with
searchedSchool = schoolnames.filter { $0.range(of: searchText, options: .caseInsensitive) != nil }
I think you have to make your searchBar implement the containsString method to achieve what you need. For reference look at this link
How do I get the correct viewcontroller after selecting a tableview cell can you please help me I think there is a problem with my index
import UIKit
var NamesC = [ "one", "two"]
var IndexC = 0
class ComputerVC: UIViewController ,UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate{
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var TableView: UITableView!
var data = [ "one", "two"]
var filteredData = [String]()
var inSearchMode = false
override func viewDidLoad() {
super.viewDidLoad()
TableView.delegate = self
TableView.dataSource = self
searchBar.delegate = self
searchBar.returnKeyType = UIReturnKeyType.done
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if inSearchMode {
return filteredData.count
}
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? DataCellC {
let text: String!
if inSearchMode {
text = filteredData[indexPath.row]
} else {
text = data[indexPath.row]
}
cell.congigureCell(text: text)
return cell
} else {
return UITableViewCell()
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
inSearchMode = false
view.endEditing(true)
TableView.reloadData()
} else {
inSearchMode = true
filteredData = data.filter({$0.contains(searchBar.text!)})
TableView.reloadData()
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
IndexC = indexPath.row
performSegue(withIdentifier: "segue", sender: self)
}
}
You can try
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
var text = ""
if inSearchMode {
text = filteredData[indexPath.row]
let correctIndex = data.index(of:filteredData[indexPath.row])
print("selected value is : \(correctIndex)")
} else {
text = data[indexPath.row]
}
performSegue(withIdentifier: "segue", sender: text)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let secondController = segue.destination as? SecondController {
secondController.sendedValue = sender as! String
}
}
class secondController:UIViewController
{
var sendedValue:String?
}
Issue is here:
Name.text = NamesC[IndexC]
Your array is filtered, your indexC is the one you used in the filtered array.
You can define a filtered array FNamesC, and update this array when you search in your search bar and in your viewController
Name.text = FNamesC[IndexC]
I don't know why you want a global array though.