UISearchBarController "works", but doesn't display on screen - ios

I have a CoreData project written in Swift that I've added a UISearchController to. I set breakpoints to see if the search is working and I can print out a list of filteredObjects from lldb in the console, but they don't display on the view.
Basically, the cells in my app load fine when the the searchBar isn't in use, but the moment I type something in the searchBar, I've verified the searchPredicate's being set and objects are being added to my filteredObjects array, but I can't figure out why they're not going on the screen. I think my problem "lives" here:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
if searchPredicate == nil {
self.configureCell(cell, atIndexPath: indexPath)
} else {
// configure the cell based on filteredObjects data
if let note = self.filteredObjects?[indexPath.row] {
cell.textLabel?.text = note.noteTitle
}
return cell
}
If there's no predicate, it's cellForRowAtIndexPath is returning what it finds in the managedObjectContext. If there IS a searchPredicate, I need to return a the info from the Note object in the filteredObjects array from the searchResultsController.
I think I know "what" I need to do, but I don't know the "how" part. I'm having trouble figuring out how to do this. Thanks for reading. If you've got any ideas, I would be grateful.
Here's my Note object:
class Note: NSManagedObject {
#NSManaged var dateCreated: NSDate
#NSManaged var dateEdited: NSDate
#NSManaged var noteTitle: String
#NSManaged var noteBody: String
}
Another potential "problem area" could be my configureCell method:
func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) {
let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject
cell.textLabel!.text = object.valueForKey("noteTitle")!.description
}
I've got the numberOfRowsInSection set here:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.searchPredicate == nil {
let sectionInfo = self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo
return sectionInfo.numberOfObjects
} else {
return filteredObjects?.count ?? 0
}
}
Here are the relevant properties I've got setup and the viewDidLoad:
class MasterViewController: UITableViewController,
NSFetchedResultsControllerDelegate, UISearchControllerDelegate,
UISearchResultsUpdating, UISearchBarDelegate
// Properties
var detailViewController: DetailViewController? = nil
var managedObjectContext: NSManagedObjectContext? = nil
// Properties for UISearchController
var searchController: UISearchController!
var searchPredicate: NSPredicate?
var filteredObjects : [Note]? = nil
Here's my viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.leftBarButtonItem = self.editButtonItem()
if let split = self.splitViewController {
let controllers = split.viewControllers
let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
self.detailViewController = controllers[controllers.count-1].topViewController as? DetailViewController
}
// UISearchController setup
searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.searchResultsUpdater = self
searchController.delegate = self
searchController.searchBar.sizeToFit()
self.tableView.tableHeaderView = searchController?.searchBar
self.tableView.delegate = self
self.definesPresentationContext = true
}
This delegate method gets called when the search bar's text the search bar becomes first responder.
// MARK: - UISearchResultsUpdating Delegate Method
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchText = self.searchController?.searchBar.text // steve put breakpoint
println(searchController.searchBar.text)
if let searchText = searchText {
searchPredicate = NSPredicate(format: "noteBody contains[c] %#", searchText)
filteredObjects = self.fetchedResultsController.fetchedObjects?.filter() {
return self.searchPredicate!.evaluateWithObject($0)
} as! [Note]?
self.tableView.reloadData()
println(searchPredicate)
}
}
When the searchBar is cancelled, everything goes back to normal when I use this delegate method to set the searchPredicate & filteredObjects back to nil
// MARK: - UISearchBar Delegate methods
func searchBar(searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
updateSearchResultsForSearchController(self.searchController)
}
func didDismissSearchController(searchController: UISearchController) {
println("didDismissSearchController")
self.searchPredicate = nil
self.filteredObjects = nil
self.tableView.reloadData()
}

Related

UISearchBar to filter custom UITableViewCells

I am trying to filter custom UITableViewCells, based on one of the UILabels present in the cell.
All of the data is parsed from remote JSON, which has the model locally named Item and currently I am displaying the following in each cell successfully:
var titleLabel = UILabel()
var descriptionLabel = UILabel()
Also defined in my ItemTableViewCell class is:
func set(product: Item) {
DispatchQueue.main.async { [weak self] in
self?.titleLabel.text = item.title
self?.descriptionLabel.text = item.listPrice
}
}
These are called within my main View Controller, to which I have added a searchBar successfully and is visible within the app:
import Foundation
import UIKit
class ItemsListViewController: UIViewController {
var items = [Items]()
var itemsSearch: [Items] = []
var tableView = UITableView()
var searchController = UISearchController()
struct Cells {
static let itemCell = "ItemCell"
}
override func viewDidLoad() {
super.viewDidLoad()
configureTableView()
configureSearchController()
}
func configureSearchController() {
searchController = UISearchController(searchResultsController: nil)
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
searchController.searchBar.placeholder = "Search items"
definesPresentationContext = true
var isSearchBarEmpty: Bool {
return searchController.searchBar.text?.isEmpty ?? true
}
}
func configureTableView() {
view.addSubview(tableView)
setTableViewDelegates()
tableView.rowHeight = 100
tableView.pin(to: view)
tableView.register(itemTableViewCell.self, forCellReuseIdentifier: Cells.itemCell)
}
func setTableViewDelegates() {
tableView.delegate = self
tableView.dataSource = self
}
}
extension itemsListViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Cells.itemCell) as! itemTableViewCell
let item = items[indexPath.row]
cell.set(item: item)
return cell
}
}
extension ItemsListViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
}
}
How can I filter these cells so say for instance a user searches for "Food", the only items which return would be ones which have a cell titleLabel = "food"?
I've tried to implement a function similar to the below, however it fails to achieve what I am after:
func filterContentForSearchText(_ searchText: String, category: = nil) {
let cell = tableView.dequeueReusableCell(withIdentifier: Cells.productCell) as! ProductTableViewCell
productsSearch = products.filter { (cell: cell) -> Bool in
return products.name.lowercased().contains(searchText.lowercased())
}
tableView.reloadData()
}
Thanks in advance for any help here.
You can not filter TableViewCells. You have to filter your model data and instead of using UISearchResultsUpdating you should use UISearchBarDelegate
I have modified your code, check it.
class ItemsListViewController: UIViewController {
var items = [Items]()
var itemsSearch: [Items] = []
var filterActive = false
var tableView = UITableView()
var searchController = UISearchController()
struct Cells {
static let itemCell = "ItemCell"
}
override func viewDidLoad() {
super.viewDidLoad()
configureTableView()
configureSearchController()
}
func configureSearchController() {
searchController = UISearchController(searchResultsController: nil)
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
searchController.searchBar.placeholder = "Search items"
searchController.searchBar.delegate = self
definesPresentationContext = true
}
func configureTableView() {
view.addSubview(tableView)
setTableViewDelegates()
tableView.rowHeight = 100
tableView.pin(to: view)
tableView.register(itemTableViewCell.self, forCellReuseIdentifier: Cells.itemCell)
}
func setTableViewDelegates() {
tableView.delegate = self
tableView.dataSource = self
}
}
extension ItemsListViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filterActive ? itemsSearch.count : items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Cells.itemCell) as! itemTableViewCell
let item = filterActive ? itemsSearch[indexPath.row] : items[indexPath.row]
cell.set(item: item)
return cell
}
}
extension ItemsListViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filterItems(text: searchController.searchBar.text)
}
func filterItems(text: String?) {
guard let text = text else {
filterActive = false
self.tableView.reloadData()
return
}
self.itemsSearch = self.items.filter({ (item) -> Bool in
return item.title.lowercased().contains(text.lowercased())
})
filterActive = true
self.tableView.reloadData()
}
// Edited Version
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.text = nil
filterActive = false
self.tableView.reloadData()
}
}
You are doing it the wrong way.
You should not filter the cells, but instead filter the model (e.g. add and remove entries to the table view data source)
Then call reloadData - this will then access the filtered model (in cellForRowAt and create only the filtered amount of (visible) cells
To improve, use a diffable data source

How could I add UISwitch to Uitableview

I could not find any way to add switch button in my tableview
I have a search bar which works perfectly,
and now I want to add a toggle button to enable "AND" "OR" filter in my search.
import Foundation
import UIKit
class TableViewController: UITableViewController, UISearchResultsUpdating {
var appDel: AppDelegate!
// var customTabBarItem:UITabBarItem = UITabBarItem(title: nil, image: UIImage(named: "my.png")?.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal), selectedImage: UIImage(named: "my.png"))
var filteredTableData = [String]()
var resultSearchController = UISearchController()
override func viewDidLoad() {
self.edgesForExtendedLayout = UIRectEdge.None
UIApplication.sharedApplication().statusBarHidden = true
appDel = UIApplication.sharedApplication().delegate as? AppDelegate
// tabBarItem = customTabBarItem
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
// Reload the table
self.tableView.reloadData()
print("TableView called!!")
}
func updateSearchResultsForSearchController(searchController: UISearchController)
{
//refer to http://www.ioscreator.com/tutorials/add-search-table-view-tutorial-ios8-swift
filteredTableData.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = ((appDel.dataFetcher?.appTitles)! as NSArray).filteredArrayUsingPredicate(searchPredicate)
filteredTableData = array as! [String]
self.tableView.reloadData()
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (self.resultSearchController.active) {
return self.filteredTableData.count
}
else if appDel.dataFetcher?.appTitles == nil {
return 0
}else{
return (appDel.dataFetcher?.appTitles?.count)!
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("appCell", forIndexPath: indexPath)
let currentItem: String = (appDel.dataFetcher?.appTitles?[indexPath.row])!
if (self.resultSearchController.active) {
cell.textLabel?.text = filteredTableData[indexPath.row]
return cell
}
else {
cell.textLabel?.text = currentItem
cell.detailTextLabel?.text = String(indexPath.row)
return cell
}
}
}
Create a table header view by dragging a UIView to the top of the tableView, just above the first prototype cell. See this question for an example.
There you can place your UISwitchView and create an outlet to your VC.

Having trouble implementing updateSearchResultsForSearchController in UISearchController?

I'm having a difficult time trying to implement updateSearchResultsForSearchController in a UISearchController. It has to do with how I implemented my original array. I'm not sure how to use that array to find the searched text.
Here's a snippet of my code:
Test.swift:
struct Test
{
let name: String
let hobby: String
}
Main.swift:
var resultsSearchController = UISearchController()
var filteredData: [Test] = [Test]()
var data: [Test] = [Test]()
override func viewDidLoad()
{
resultsSearchController = UISearchController(searchResultsController: nil)
definesPresentationContext = true
resultsSearchController.dimsBackgroundDuringPresentation = true
resultsSearchController.searchResultsUpdater = self
tableView.tableHeaderView = resultsSearchController.searchBar
data = [
Test(name: "Abby", hobby: "Games"),
Test(name: "Brian", hobby: "TV"),
Test(name: "Ced", hobby: "Gym"),
Test(name: "David", hobby: "Fun")]
tableView.dataSource = self
tableView.delegate = self
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if (resultsSearchController.active && resultsSearchController.searchBar.text != "")
{
return filteredData.count
}
return data.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cell: UITableViewCell!
// Dequeue the cell to load data
cell = tableView.dequeueReusableCellWithIdentifier("Funny", forIndexPath: indexPath)
let example: Test
if resultsSearchController.active && resultsSearchController.searchBar.text != ""
{
example = filteredData[indexPath.row]
}
else
{
example = data[indexPath.row]
}
return cell
}
func updateSearchResultsForSearchController(searchController: UISearchController)
{
filteredData.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "hobby CONTAINS[c] %#", resultsSearchController.searchBar.text!)
// WHAT ELSE TO DO?
tableView.reloadData()
}
I'm just a bit confused on how to use my data to return back the correct searched results in updateSearchResultsForSearchController.
Can someone point me in the right direction?
Thanks
What do you need to to is make the search in your data source and return the data nothing else, something like this code:
func updateSearchResultsForSearchController(searchController: UISearchController) {
filteredData.removeAll(keepCapacity: false)
let searchedData = resultsSearchController.searchBar.text
// find elements that contains the searchedData string for example
filteredData = data.filter { $0.name.containsString(searchedData) }
tableView.reloadData()
}
If you want to perform another type of search regarding another field of your struct you can modify the filter like you like. Once you call the tableView.reloadData() all is going to be reloaded again.
I hope this help you.

Using UISearchController to search a UITableView in a different View Controller

I'm trying to implement a UISearchController to reference and search for items in a UITableView from another View Controller. In other words, I'd like to have search bar in VC1 to search for items in the UITableView of VC2.
The reason I'm doing this is because I'd like to have search functionality right when my app opens, i.e. in VC1 instead of having to go VC1->V2 to search for items in VC2.
I have already created some simple code to search for items in the UITableView of VC1, but how can I reference the UITableView of VC2 instead?
Here's my sample code for UISearchController implementation:
let tableData = ["One","Two","Three","Twenty-One"]
var filteredTableData = [String]()
var resultSearchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
// Reload the table
self.tableView.reloadData()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (self.resultSearchController.active && self.resultSearchController.searchBar.text != "") {
return self.filteredTableData.count
}
else {
return self.tableData.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
if (self.resultSearchController.active && self.resultSearchController.searchBar.text != "") {
filteredTableData = tableData
cell.textLabel?.text = filteredTableData[indexPath.row]
return cell
}
else {
cell.textLabel?.text = tableData[indexPath.row]
return cell
}
}
func updateSearchResultsForSearchController(searchController: UISearchController)
{
filteredTableData.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (tableData as NSArray).filteredArrayUsingPredicate(searchPredicate)
filteredTableData = array as! [String]
self.tableView.reloadData()
}
FYI: Both my VC1 and VC2 are connected via a UINavigationController
Thanks!

Search Bar controller in TableView

Hello I am trying to implement a SearchBarController but If I type something in the search bar data doesn't comes up according to the letters typed. Here is my code
class CountriesTableViewController: UITableViewController, UISearchResultsUpdating {
var dict = NSDictionary()
var filterTableData = NSDictionary()
var resultSearchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
getCountriesNamesFromServer()
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
self.tableView.reloadData()
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(self.resultSearchController.active){
return self.filterTableData.count-1
}else {
return dict.count-1
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CountriesTableViewCell
if(self.resultSearchController.active){
// (cell.contentView.viewWithTag(1) as! UILabel).text = filterTableData[indexPath.row]
cell.countryNameLabel.text = (((filterTableData["\(indexPath.item)"] as?NSDictionary)!["Countries"] as?NSDictionary)!["name"] as?NSString)! as String
return cell
}else{
cell.countryNameLabel.text = (((dict["\(indexPath.item)"] as?NSDictionary)!["Countries"] as?NSDictionary)!["name"] as?NSString)! as String
return cell
}
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
let keys: NSArray = dict.allKeys
let filteredKeys: [String] = keys.filteredArrayUsingPredicate(NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)) as! [String]
filterTableData = dict.dictionaryWithValuesForKeys(filteredKeys)
}
I think Problem is in this function updateSearchResultsForSearchController because first I think I have to remove the object from Nsdictionary. I know in Array we do this
filterTableData.removeAll(keepCapacity: false)
and I think for NSDictionary I have to use NSMUtableDictionary But I don't know if that is the problem. If it is the problem still don't know how can I remove objects from NSMutableDictionary and in short to make this search functionality workable.
Note: Data is Populating successfully in tableView.. just the search functionality is not working

Resources