Adding search bar programmatically to tableview in swift - ios

I have an textfield which represents an tableview as its inputview. I want to add 2 things to this tableview.
1) add a search bar.
2) add cancell button to top of tableview.
class enterYourDealVC: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UISearchDisplayDelegate, UISearchResultsUpdating {
var tableView: UITableView = UITableView()
let searchController = UISearchController(searchResultsController: nil)
var dealAirports = [
airPorts(name: "Airport1", shortcut: "AP1")!),
airPorts(name: "Airport2", shortcut: "AP2")!)
]
var filteredAirports = [airPorts]()
//view did load
tableView = UITableView(frame: UIScreen.mainScreen().bounds, style: UITableViewStyle.Plain)
tableView.delegate = self
tableView.dataSource = self
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
toTextField.inputView = self.tableView
//here is my search function
func filterContentForSearchText(searchText: String, scope: String = "All") {
filteredAirports = dealAirports.filter { ap in
return ap.name.lowercaseString.containsString(searchText.lowercaseString)
}
tableView.reloadData()
}
}
The problem is with this code, it doesn't search. Also when I click the search bar it dismiss the tableview and returns me back to viewcontroller. How can I fix this?
and how can I add cancel button to this tableview?

This will add a SeachBar
lazy var searchBar:UISearchBar = UISearchBar()
override func viewDidLoad()
{
searchBar.searchBarStyle = UISearchBar.Style.default
searchBar.placeholder = " Search..."
searchBar.sizeToFit()
searchBar.isTranslucent = false
searchBar.backgroundImage = UIImage()
searchBar.delegate = self
navigationItem.titleView = searchBar
}
func searchBar(_ searchBar: UISearchBar, textDidChange textSearched: String)
{
...your code...
}

In swift 4.1 and Xcode 9.4.1
Step1
Add UISearchBarDelegate to your view controller.
Step2
//Write this code in viewDidLoad() or your required function
let searchBar:UISearchBar = UISearchBar()
//IF you want frame replace first line and comment "searchBar.sizeToFit()"
//let searchBar:UISearchBar = UISearchBar(frame: CGRect(x: 10, y: 10, width: headerView.frame.width-20, height: headerView.frame.height-20))
searchBar.searchBarStyle = UISearchBarStyle.prominent
searchBar.placeholder = " Search..."
searchBar.sizeToFit()
searchBar.isTranslucent = false
searchBar.backgroundImage = UIImage()
searchBar.delegate = self
yourViewName.addSubview(searchBar)//Here change your view name
Step3
func searchBar(_ searchBar: UISearchBar, textDidChange textSearched: String) {
//your code here....
}

Swift 4+
class SearchViewController: UIViewController {
#IBOutlet weak var maintableView: UITableView!
#IBOutlet weak var searchUIBar: UISearchBar!
var isSearch : Bool = false
var tableData = ["Afghanistan", "Algeria", "Bahrain","Brazil", "Cuba", "Denmark","Denmark", "Georgia", "Hong Kong", "Iceland", "India", "Japan", "Kuwait", "Nepal"];
var filteredTableData:[String] = []
override func viewDidLoad() {
super.viewDidLoad()
maintableView.dataSource = self
maintableView.delegate = self
searchUIBar.delegate = self
maintableView.reloadData()
}
}
extension SearchViewController: UISearchBarDelegate{
//MARK: UISearchbar delegate
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
isSearch = true
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
isSearch = false
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
isSearch = false
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
isSearch = false
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.count == 0 {
isSearch = false
self.maintableView.reloadData()
} else {
filteredTableData = tableData.filter({ (text) -> Bool in
let tmp: NSString = text as NSString
let range = tmp.range(of: searchText, options: NSString.CompareOptions.caseInsensitive)
return range.location != NSNotFound
})
if(filteredTableData.count == 0){
isSearch = false
} else {
isSearch = true
}
self.maintableView.reloadData()
}
}
}
extension SearchViewController: UITableViewDelegate{
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = maintableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if (isSearch) {
cell.textLabel?.text = filteredTableData[indexPath.row]
return cell
}
else {
cell.textLabel?.text = tableData[indexPath.row]
print(tableData[indexPath.row])
return cell
}
}
}
extension SearchViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(isSearch) {
return filteredTableData.count
}else{
return tableData.count
}
}
}

Here is code snipet in swift for the same For more just refer Apple Doc mentioned in comment.
UISearchDisplayController is deprecated in IOS8.0, and recommended to use UISearchController
Hope this will help you alot
ContactTVC
class ContactTVC:UITableViewController{
// MARK: Types
/// State restoration values.
enum RestorationKeys : String {
case viewControllerTitle
case searchControllerIsActive
case searchBarText
case searchBarIsFirstResponder
}
struct SearchControllerRestorableState {
var wasActive = false
var wasFirstResponder = false
}
/*
The following 2 properties are set in viewDidLoad(),
They an implicitly unwrapped optional because they are used in many other places throughout this view controller
*/
/// Search controller to help us with filtering.
var searchController: UISearchController!
/// Secondary search results table view.
var resultsTableController: ResultsTableController!
/// Restoration state for UISearchController
var restoredState = SearchControllerRestorableState()
var arrayContacts: Array<CNContact> = []
var searchResultArrayContacts: Array<CNContact> = []
override func viewDidLoad() {
super.viewDidLoad()
resultsTableController = ResultsTableController()
// We want to be the delegate for our filtered table so didSelectRowAtIndexPath(_:) is called for both tables.
resultsTableController.tableView.delegate = self
searchController = UISearchController(searchResultsController: resultsTableController)
searchController.searchResultsUpdater = self
searchController.searchBar.sizeToFit()
tableView.tableHeaderView = searchController.searchBar
searchController.delegate = self
searchController.dimsBackgroundDuringPresentation = false // default is YES
searchController.searchBar.delegate = self // so we can monitor text changes + others
/*
Search is now just presenting a view controller. As such, normal view controller
presentation semantics apply. Namely that presentation will walk up the view controller
hierarchy until it finds the root view controller or one that defines a presentation context.
*/
definesPresentationContext = true
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// Restore the searchController's active state.
if restoredState.wasActive {
searchController.active = restoredState.wasActive
restoredState.wasActive = false
if restoredState.wasFirstResponder {
searchController.searchBar.becomeFirstResponder()
restoredState.wasFirstResponder = false
}
}
}
//MARK override TableViewDelegates/Datasource methods
}
extension ContactTVC: UISearchResultsUpdating{
// MARK: UISearchResultsUpdating
func updateSearchResultsForSearchController(searchController: UISearchController) {
if let text = searchController.searchBar.text where (text.isEmpty == false){
{
// Hand over the filtered results to our search results table.
let resultsController = searchController.searchResultsController as! ResultsTableController
resultsController.filteredProducts = Array(searchResult)
resultsController.tableView.reloadData()
dispatch_async(dispatch_get_main_queue(), {
self.tableViewContacts.reloadData()
})
}
}
}
}
//MARK: SearchBarDelegate
extension ContactTVC: UISearchBarDelegate{
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
if let text = searchBar.text where (text.isEmpty == false) {
// update the search result array by filtering….
if searchResult.count > 0{
self.searchResultArrayContacts = Array(searchResult)
}
else{
self.searchResultArrayContacts = Array(self.arrayContacts)
}
dispatch_async(dispatch_get_main_queue(), {
self.tableViewContacts.reloadData()
})
}
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
searchBar.text = nil
searchBar.resignFirstResponder()
}
}
/// The table view controller responsible for displaying the filtered products as the user types in the search field.
class ResultsTableController: UITableViewController {
// MARK: Properties
let reusableIdentifier = "contactCell"
var filteredProducts = [CNContact]()
override func viewDidLoad() {
self.tableView.emptyDataSetSource = self
self.tableView.emptyDataSetDelegate = self
}
// MARK: UITableViewDataSource
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredProducts.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .Subtitle, reuseIdentifier: reusableIdentifier)
var contact = CNContact()
contact = filteredProducts[indexPath.row]
// Configure the cell...
cell.textLabel?.text = contact.givenName
let phones = contact.phoneNumbers[0].value as! CNPhoneNumber
cell.detailTextLabel?.text = phones.stringValue
return cell
}
}
Thanks

//first write delegate for search "UISearchBarDelegate"
//MARK:- Search button action
#IBAction func searchWithAddress(_ sender: Any) {
let searchController = UISearchController(searchResultsController: nil)
searchController.searchBar.delegate = self
self.present(searchController, animated: true, completion: nil)
}

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

Create SearchController for reusable use

Is it possible to create UISearchController for reusable use in many classes?
To use a minimum of code in new classes.
Now I have code in all classes, but I want to use minimum code in all classes to call searchController:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
#IBOutlet weak var tableView: UITableView!
var photoBook: [PhotoBooking] = []
var filteredBook: [PhotoBooking] = []
private var searchController = UISearchController(searchResultsController: nil)
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 viewDidLoad() {
super.viewDidLoad()
configureSearchController()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering {
return filteredBook.count
}
return photoBook.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
var pBook: PhotoBooking
if isFiltering {
pBook = filteredBook[indexPath.row]
} else {
pBook = photoBook[indexPath.row]
}
cell.textLabel?.text = pBook.invoiceID
cell.detailTextLabel?.text = pBook.contactInfo["surname"] as! String
return cell
}
private func configureSearchController() {
searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.searchBar.barTintColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.delegate = self
tableView.tableHeaderView = searchController.searchBar
definesPresentationContext = true
}
}
extension AllPhotoBookingsViewController: UISearchResultsUpdating {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
filterContentForSearchText(searchBar.text!)
tableView.reloadData()
}
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchController.searchBar.text!)
tableView.reloadData()
}
private func filterContentForSearchText(_ searchText: String) {
filteredBook = photoBook.filter({ (book: PhotoBooking) -> Bool in
let name = book.contactInfo["name"] as! String
let surname = book.contactInfo["surname"] as! String
let invoice = book.invoiceID
return invoice.localizedCaseInsensitiveContains(searchText) ||
name.localizedCaseInsensitiveContains(searchText) ||
surname.localizedCaseInsensitiveContains(searchText)
})
tableView.reloadData()
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
navigationController?.hidesBarsOnSwipe = false
}
}
Maybe need use protocol, but I don't understand how to use correctly protocol.
There are many ways of doing this, using protocols is a great approach, but you could also subclass UIViewController and make a BaseSearchableViewController where you would add all the functionality you need for your search, then, you just subclass from BaseSearchableViewController instead of UIViewController.

Unable to present a UISearchController

I have the following view hierarchy:
UINavigationController
||
\/
LibraryTableViewController: UITableViewController
||
\/
AlbumsCollectionViewController: UICollectionViewController
||
\/
SongsTableViewController: UITableViewController
I want to have a search bar in AlbumsCollectionViewController and a different one in SongsTableViewController that is shown in the navigationItem.titleView.
I have managed to add a working search bar in AlbumsCollectionViewController as follows:
class AlbumsCollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout, UISearchControllerDelegate, UISearchResultsUpdating, UISearchBarDelegate {
var searchController : UISearchController!
override func viewDidLoad() {
super.viewDidLoad()
initSearchBar()
initNavigationBar()
}
private func initSearchBar() {
self.searchController = UISearchController(searchResultsController: nil)
self.searchController.searchResultsUpdater = self
self.searchController.delegate = self
self.searchController.searchBar.delegate = self
self.searchController.hidesNavigationBarDuringPresentation = false
self.searchController.dimsBackgroundDuringPresentation = false
searchController.searchResultsController?.view.isHidden = false
searchController.hidesNavigationBarDuringPresentation = false
self.extendedLayoutIncludesOpaqueBars = true
self.definesPresentationContext = true
searchController.searchBar.backgroundColor = UIColor.black
UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).setTitleTextAttributes([NSAttributedStringKey.foregroundColor : UIColor.white], for: .normal)
self.navigationItem.titleView = searchController.searchBar
navigationItem.titleView?.isHidden = true
}
private func initNavigationBar() {
searchButton.tintColor = UIColor.white
settingsButton.tintColor = UIColor.white
backButton.tintColor = UIColor.white
self.navigationItem.title = "Artists"
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor : UIColor.white]
}
#IBAction func SearchButtonTapped(_ sender: Any) {
showSearchBar()
}
private func showSearchBar(){
navigationItem.titleView?.isHidden = false
searchController.isActive = true
}
}
Note that the search bar is hidden on ViewDidLoad() and is presented when a button is pressed as shown in SearchButtonTapped method.
Now, I am trying to do the same in SongsTableViewController however, the search bar is not showing when tapping the the button (i.e. calling SearchButtonTapped) and I am getting the following message:
Warning: Attempt to present <UISearchController: 0x7f8158812b50> on <MyProject.AlbumsCollectionViewController: 0x7f81588023c0> whose view is not in the window hierarchy!
If I commented the line searchController.isActive = true then the search bar will show, however, it wont be active even if I tapped on it.
Edit
Sorry if I haven't been clear. I have a separate UISearchController in SongsTableViewController. I meant I am using the same logic in both controllers
Also Note if I pushed SongsTableViewController from the navigation controller (i.e the view hierarchy only has 2 controllers (UINavigationController => SongsTableViewController) the search bar works fine
This is most of the Code of SongsTableViewController (omitted non relevant stuff)
import UIKit
import os.log
import MediaPlayer
class SongsTableViewController: UITableViewController, UISearchControllerDelegate, UISearchResultsUpdating, UISearchBarDelegate ,PlayerDelegate, NowPlayingDelegate, SongCellDelegate, SongsOptionsDelegate {
// MARK: properties
var playerManager: PlayerManager? = nil
var dataManager: DataManager? = nil
var tabVC: TabBarController?
var selectedSong: Song?
lazy var optionsTransitionDelegate = PresentationManager()
lazy var playlistTransitionDelegate = PresentationManager()
var searchController : UISearchController!
#IBOutlet var backgroundView: UIView!
#IBOutlet weak var searchButton: UIBarButtonItem!
#IBOutlet weak var settingsButton: UIBarButtonItem!
var albumID: String?
var artistID: String?
var playlist: Playlist?
var songs = [Song]()
var songIndexMap = [String: Int]()
var filteredSongs = [Song]()
override func viewDidLoad() {
super.viewDidLoad()
self.dataManager = DataManager.getInstance()
self.playerManager = PlayerManager.getInstance()
playlistTransitionDelegate.screenRatio = 2.0 / 3.0
if(self.albumID != nil) {
self.songs = SQLiteManager.getAlbumSongs(albumID: self.albumID!)
} else if(self.artistID != nil) {
self.songs = SQLiteManager.getArtistSongs(artistID: self.artistID!)
} else if (self.playlist != nil) {
self.songs = SQLiteManager.getPlaylistSongs(playlist: self.playlist!)
} else {
dataManager?.songsTableViewController = self
}
for i in 0..<songs.count {
songIndexMap[songs[i].id] = i
}
initSearchBar()
initNavigationBar()
if(songs.count == 0 && (!fullListOfSongs() || fullListOfSongs() && dataManager?.getFullSongsCount() == 0)){
tableView.backgroundView = backgroundView
}
tableView.tableFooterView = UIView()
tabVC = tabBarController as? TabBarController
tabVC?.nowPlayingViewController?.delegate = self
}
private func shouldAutorotate() -> Bool {
return false
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return Util.SONG_CELL_HEIGHT
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(isFiltering()) {
return self.filteredSongs.count
} else if (fullListOfSongs()) {
return dataManager!.getFullSongsCount()
}
return self.songs.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "SongTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? SongCell else {
fatalError("The dequeued cell is not an instance of SongCell.")
}
var song: Song?
if(fullListOfSongs()) {
if(isFiltering()){
song = self.filteredSongs[indexPath.row]
} else {
song = dataManager?.getSong(index: indexPath.row)
}
} else {
if(isFiltering()){
song = self.filteredSongs[indexPath.row]
} else {
song = self.songs[indexPath.row]
}
}
cell.setAttributes(song: song!)
cell.delegate = self
cell.preservesSuperviewLayoutMargins = false
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
self.tableView.deselectRow(at: indexPath, animated: false)
}
// MARK: - Search Bar
func updateSearchResults(for searchController: UISearchController) {
if (!searchController.isActive) {
hideSearchBar()
tableView.reloadData()
}
if(isSearchBarEmpty()) {
return
}
filterSongs(filter: searchController.searchBar.text!)
tableView.reloadData()
}
private func filterSongs(filter: String) {
if(self.albumID != nil) {
self.filteredSongs = SQLiteManager.getAlbumSongs(albumID: self.albumID!, filter: filter)
} else if(self.artistID != nil) {
self.filteredSongs = SQLiteManager.getArtistSongs(artistID: self.artistID!, filter: filter)
} else if(self.playlist != nil) {
self.filteredSongs = SQLiteManager.getPlaylistSongs(playlist: self.playlist!, filter: filter)
}else {
self.filteredSongs = SQLiteManager.getSongs(filter: filter)
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText == "" {
tableView.reloadData()
}
}
private func initSearchBar() {
self.searchController = UISearchController(searchResultsController: nil)
self.searchController.searchResultsUpdater = self
self.searchController.delegate = self
self.searchController.searchBar.delegate = self
self.searchController.hidesNavigationBarDuringPresentation = false
self.searchController.dimsBackgroundDuringPresentation = false
searchController.searchResultsController?.view.isHidden = false
searchController.hidesNavigationBarDuringPresentation = false
self.extendedLayoutIncludesOpaqueBars = true
self.definesPresentationContext = true
searchController.searchBar.backgroundColor = UIColor.black
UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).setTitleTextAttributes([NSAttributedStringKey.foregroundColor : UIColor.white], for: .normal)
self.navigationItem.titleView = searchController.searchBar
navigationItem.titleView?.isHidden = true
}
private func initNavigationBar() {
searchButton.tintColor = UIColor.white
if (fullListOfSongs()) {
searchButton.isEnabled = false
dataManager?.buttons.append(searchButton)
}
settingsButton.tintColor = UIColor.white
self.navigationItem.title = "Songs"
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor : UIColor.white]
}
#IBAction func SearchButtonTapped(_ sender: Any) {
showSearchBar()
}
private func showSearchBar(){
self.navigationItem.titleView?.isHidden = false
self.searchController.isActive = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.searchController.searchBar.becomeFirstResponder()
}
navigationItem.rightBarButtonItems![0].isEnabled = false
navigationItem.rightBarButtonItems![0].image = nil
navigationItem.rightBarButtonItems![1].isEnabled = false
navigationItem.rightBarButtonItems![1].image = nil
}
private func hideSearchBar() {
navigationItem.titleView?.isHidden = true
navigationItem.rightBarButtonItems![0].isEnabled = true
navigationItem.rightBarButtonItems![0].image = UIImage(named: "settings")
navigationItem.rightBarButtonItems![1].isEnabled = true
navigationItem.rightBarButtonItems![1].image = UIImage(named: "search")
}
func isFiltering() -> Bool {
if(searchController == nil){
return false
}
return searchController.isActive && !isSearchBarEmpty()
}
private func isSearchBarEmpty() -> Bool {
return searchController.searchBar.text?.isEmpty ?? true
}
private func fullListOfSongs() -> Bool {
return self.playlist == nil && self.albumID == nil && self.artistID == nil
}
}
This worked when I tested. I believe the problem is in SongsTableViewController: self.definesPresentationContext = false. This should be true for the pushed View Controller. (see docs here)
For SongsTableViewController (pushed view controller) add the following:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.definesPresentationContext = true
}
And add this to AlbumsCollectionViewController (initial view controller):
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.definesPresentationContext = false
}
When you are on SongsTableViewController, your AlbumsCollectionViewController is not in the window hierarchy.
What I can understand, you are calling showSearchBar method of AlbumsCollectionViewController from SongsTableViewController. And since you navigated from AlbumsCollectionViewController to SongsTableViewController, your AlbumsCollectionViewController is not in the window hierarchy hence wont able to present the search controller.
To fix try adding a separate searchbar controller in SongsTableViewController just as you previously did in AlbumsCollectionViewController.
Alternatively you can create a seperate viewcontroller, implement search functionality and then present it from both of your controllers.

How to implement a custom search-bar when the array type is JSON

I'm working on a project in swift 3 and I have a specific UIViewController where I have a UITableView and as to populate data on its cell, the data is get from the server and I assign it to an array of the type JSON. Thus, I have a UISearch bar placed at a different place in the same UIViewController(not as the header of the UITableView). My requirement is to implement the functionality of the search bar where i could filter and search the data of my UITableView. How can I achieve this? I worked on the code half way though which is not fucntioning and the code as bellow.
import UIKit
import SwiftyJSON
import SDWebImage
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource,UISearchResultsUpdating,UISearchBarDelegate {
#IBOutlet weak var tableView: UITableView!
var count : Int = 0
var searchActive : Bool?
var selectedCategoryList = [JSON?]()
var filterSelectedCategoryList = [JSON?]()
let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
passJson()
searchController.searchResultsUpdater = self
searchController.hidesNavigationBarDuringPresentation = false
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
searchController.searchBar.sizeToFit()
self.tableView.tableHeaderView = searchController.searchBar
searchController.searchBar.delegate = self
self.tableView.reloadData()
// Do any additional setup after loading the view, typically from a nib.
}
func updateSearchResults(for searchController: UISearchController) {
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filterSelectedCategoryList = selectedCategoryList.filter { titles in
return (titles?["title"].stringValue.lowercased().contains(searchText.lowercased()))!
}
tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func passJson() {
let path : String = Bundle.main.path(forResource: "JsonFile", ofType: "json") as String!
let jsonData = NSData(contentsOfFile: path) as NSData!
let readableJson = JSON(data: jsonData as! Data, options: JSONSerialization.ReadingOptions.mutableContainers, error: nil)
let json = readableJson
print(json)
let selectedItemArray = json["itemList"].arrayValue
self.selectedCategoryList = selectedItemArray
print("new prints",self.selectedCategoryList)
// print( "Count",selectedItemArray?.count as Any)
self.count = self.selectedCategoryList.count
print(self.count)
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.searchController.isActive && searchController.searchBar.text != ""{
return filterSelectedCategoryList.count
}else{
return selectedCategoryList.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell
if count > 0 {
if self.searchController.isActive && searchController.searchBar.text != ""{
cell.label.text = filterSelectedCategoryList[indexPath.row]?["healerName"].stringValue
}else{
cell.label.text = selectedCategoryList[indexPath.row]?["healerName"].stringValue
}
}
return cell
}
}
Try to implement the UISearch​Bar​Delegate method but before that set the searchBar.delegate to self in your viewDidLoad.
override func viewDidLoad() {
super.viewDidLoad()
passJson()
searchController.searchResultsUpdater = self
searchController.hidesNavigationBarDuringPresentation = false
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
searchController.searchBar.sizeToFit()
//Set the delegate of searchBar
searchController.searchBar.delegate = self
self.tableView.reloadData()
}
Now implement the search​Bar(_:​text​Did​Change:​) method of UISearchBarDelegate and filter your array in that method.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filterSelectedCategoryList = selectedCategoryList.filter { titles in
return (titles?["healerName"].stringValue.lowercased().contains(searchText.lowercased()))!
}
tableView.reloadData()
}

Why is the search not working?

I have an array and I am trying to do a real time search. So originally I will have the unfiltered array and as I search the table view will update as I type. I have set the delegate, so I am lost on why it is not working. Any help is appreciated :)
#IBOutlet weak var table: UITableView!
var searchController = UISearchController()
var unfilitered = ["cat", "dog", "bat", "tiger"]
var filtered = [String]()
var shouldShowSearchResults = false
override func viewDidLoad() {
super.viewDidLoad()
configureSearchController()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: searchcell = table.dequeueReusableCellWithIdentifier("CELL") as! searchcell
if shouldShowSearchResults {
cell.animal.text = filtered[indexPath.row]
}
else {
cell.animal.text = unfiltered[indexPath.row]
}
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if shouldShowSearchResults {
return filtered.count
}
else {
return unfiltered.count
}
}
func configureSearchController() {
searchController.searchBar.delegate = self
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController = UISearchController(searchResultsController: nil)
searchController.searchBar.placeholder = "Search"
searchController.searchBar.sizeToFit()
table.tableHeaderView = searchController.searchBar
}
func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
shouldShowSearchResults = true
table.reloadData()
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
shouldShowSearchResults = false
table.reloadData()
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
if !shouldShowSearchResults {
shouldShowSearchResults = true
table.reloadData()
}
searchController.searchBar.resignFirstResponder()
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchString = searchController.searchBar.text
filtered = unfiltered.filter({ (animal) -> Bool in
let animalText: NSString = animal
return (animalText.rangeOfString(searchString!, options: NSStringCompareOptions.CaseInsensitiveSearch).location) != NSNotFound
})
table.reloadData()
}
The reloadData method won't force a redraw of the tableView unless it's performed on the main thread.

Resources