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.
Related
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.
I have main screen, with a button on which I segue to searchVC screen.I have a navigation controller between them, in searchVC there are searchController and searchBar.
Problem:I need to activate search when screen appears, but searchBar activation(tap or becomeFirstResponder() ) causes memory leaks(image below)
I tried to remove delegates and the problem disappears, but I need to know when cancel button pressed to segue/dismiss to mainVC
Code:tableView for results, resultView with label for empty results
class SearchViewController: UIViewController,UISearchBarDelegate,UISearchControllerDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var resultView: ResultView!
let searchController = UISearchController(searchResultsController: nil)
var filteredSongs = [SongListModel]()
var songs = SongListModel.fetchSongs()
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search songs"
if #available(iOS 11.0, *) {
navigationItem.titleView = searchController.searchBar
navigationItem.hidesSearchBarWhenScrolling = false
// navigationController?.navigationBar.topItem?.searchController = searchController
// navigationItem.titleView?.isHidden = true
searchController.dimsBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = false
} else {
tableView.tableHeaderView = searchController.searchBar
}
searchController.searchBar.showsCancelButton = true
searchController.definesPresentationContext = true
searchController.searchBar.sizeToFit()
searchController.delegate = self
searchController.searchBar.delegate = self
tableView.keyboardDismissMode = .interactive
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
delay(0.1) { [unowned self] in
self.searchController.searchBar.becomeFirstResponder()
}
}
func delay(_ delay: Double, closure: #escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
searchController.searchBar.resignFirstResponder()
}
func searchBarIsEmpty() -> Bool {
// Returns true if the text is empty or nil
return searchController.searchBar.text?.isEmpty ?? true
}
func filterContentForSearchText(_ searchText: String, scope: String = "All") {
filteredSongs = songs.filter({( song : SongListModel) -> Bool in
return song.musicFileName.lowercased().contains(searchText.lowercased())
})
tableView.reloadData()
}
func isFiltering() -> Bool {
return searchController.isActive && !searchBarIsEmpty()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchController.searchBar.endEditing(true)
searchController.searchBar.resignFirstResponder()
// searchController.searchBar.delegate = nil
// searchController.searchResultsUpdater = nil
dismiss(animated: true, completion: nil)
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "PlayTilesSegue", let destinationVC = segue.destination as? TilesViewController, let selectedIndex = tableView.indexPathForSelectedRow?.row {
let song: SongListModel
if isFiltering() {
song = filteredSongs[selectedIndex]
} else {
song = songs[selectedIndex]
}
destinationVC.songFileName = song.musicFileName
navigationController?.setNavigationBarHidden(true, animated: false)
}
}
}
extension SearchViewController: UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering() {
resultView.setIsFilteringToShow(filteredItemCount: filteredSongs.count, of: songs.count)
return filteredSongs.count
}
resultView.setNotFiltering()
return songs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "filteredCell", for: indexPath) as! FilteredSongCell
let song: SongListModel
if isFiltering() {
song = filteredSongs[indexPath.row]
} else {
song = songs[indexPath.row]
}
cell.listenSongButton.setBackgroundImage(UIImage(named: "playback"), for: .normal)
cell.filteredAuthorNameLabel.text = song.authorName
cell.filteredSongNameLabel.text = song.songName
cell.playGameButton.setTitle(song.playButton.rawValue, for: .normal)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "PlayTilesSegue", sender: indexPath)
tableView.deselectRow(at: indexPath, animated: true)
}
}
extension SearchViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchController.searchBar.text!)
}
}
Image memory leaks
Deactivating the search controller on removing VC from parent helps to avoid memory leak:
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if parent == nil, searchController.isActive {
searchController.isActive = false
}
}
You need to set the UISearchController searchBar's delegate. Once you have done this, the addition of the delegate method searchBarCancelButtonClicked: will properly be called.
Here it is.
Yes I have already tried these from other similar questions but they didn't work:
var frame = CGRect.zero
frame.size.height = .leastNormalMagnitude
tableView.tableHeaderView = UIView(frame: frame)
and
self.automaticallyAdjustsScrollViewInsets = false;
definesPresentationContext = true
Have a look at the extra space I am trying to remove below search bar:
Screenshot
Maybe you can point out an improvement in my code for the same:
import UIKit
class SelectCountryViewController: UITableViewController, UISearchBarDelegate, UISearchResultsUpdating {
struct CellStruct
{
var countryName : String
var countryFlag : String
var countryDialCode : String
}
var cellDatas = [CellStruct]()
var filteredCellDatas = [CellStruct]()
var searchController : UISearchController!
var resultsController = UITableViewController()
var refreshController = UIRefreshControl()
var searchTextField : UITextField!
var searchLoaded = false
var isSearching = false
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self;
var frame = CGRect.zero
frame.size.height = .leastNormalMagnitude
tableView.tableHeaderView = UIView(frame: frame)
self.automaticallyAdjustsScrollViewInsets = false;
//tableView.separatorStyle = UITableViewCellSeparatorStyle.singleLine
definesPresentationContext = true
configureSearchController()
let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(cancel))
navigationItem.leftBarButtonItem = cancelButton
//self.navigationItem.title = "Select Country"
let searchButton: UIBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: self, action: #selector(searchButtonAction))
searchButton.image = UIImage(named: "search")
self.navigationItem.rightBarButtonItem = searchButton
refreshController.attributedTitle = NSAttributedString(string: "")
refreshController.addTarget(self, action: #selector(refreshSelector), for: .valueChanged)
tableView.addSubview(refreshController)
guard let path = Bundle.main.path(forResource: "countries", ofType: "json")
else
{
return
}
let url = URL(fileURLWithPath: path)
do
{
let data = try Data (contentsOf: url)
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers)
print(json)
guard let array = json as? [Any] else { return}
for info in array {
guard let userDict = info as? [String: Any] else { return}
guard let code = userDict["code"] as? String else { print("No code found"); return}
guard let dialCode = userDict["dial_code"] as? String else { print("No dial code found"); return}
guard let name = userDict["name"] as? String else { print("No name found"); return}
print("We have: ", code, dialCode, name)
cellDatas.append(CellStruct(countryName: name, countryFlag: code, countryDialCode: dialCode))
}
}
catch
{
print(error)
}
}
func configureSearchController()
{
resultsController.tableView.delegate = self
resultsController.tableView.dataSource = self
self.searchController = UISearchController(searchResultsController: self.resultsController)
//self.tableView.tableHeaderView = self.searchController.searchBar
self.searchController.searchResultsUpdater = self
self.searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.delegate = self
self.searchController.searchBar.scopeButtonTitles = []
for subView in searchController.searchBar.subviews {
for subViewOne in subView.subviews {
if subViewOne is UITextField {
searchTextField = subViewOne as! UITextField
subViewOne.backgroundColor = UIColor.white
break
}
}
}
self.automaticallyAdjustsScrollViewInsets = false;
extendedLayoutIncludesOpaqueBars = true
definesPresentationContext = true
}
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
// tableView.setContentOffset(self.navigationItem, animated: true)
searchController.searchBar.barTintColor = UIColor.white
//searchController.searchBar.layer.borderColor = UIColor.white.cgColor
searchTextField.backgroundColor = UIColor.searchBarTextFieldGrey()
return true
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
self.searchController.searchBar.showsCancelButton = false
// searchController.searchBar.barTintColor = nil
searchTextField.backgroundColor = UIColor.white
searchController.searchBar.barTintColor = nil
}
override func viewWillDisappear(_ animated: Bool) {
self.navigationController?.navigationBar.shadowImage = nil
self.navigationController?.navigationBar.backIndicatorImage = nil
}
func updateSearchResults(for searchController: UISearchController) {
//tableView.separatorStyle = UITableViewCellSeparatorStyle.none
if searchController.searchBar.text! == "" {
filteredCellDatas = cellDatas
} else {
// Filter the results
filteredCellDatas = cellDatas.filter { $0.countryName.lowercased().contains(searchController.searchBar.text!.lowercased()) }
}
resultsController.tableView.reloadData()
// tableView.separatorStyle = .none
// tableView.separatorStyle = UITableViewCellSeparatorStyle.singleLine
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == resultsController.tableView
{
isSearching = true
return filteredCellDatas.count
}
else
{
isSearching = false
return cellDatas.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
cell?.separatorInset.left = 15
if cell == nil {
cell = UITableViewCell(style: .value1, reuseIdentifier: "Cell")
cell?.separatorInset.left = 0
}
if tableView == resultsController.tableView
{
cell?.textLabel?.text = filteredCellDatas[indexPath.row].countryName
cell?.detailTextLabel?.text = filteredCellDatas[indexPath.row].countryDialCode
cell?.imageView?.image = UIImage (named: filteredCellDatas[indexPath.row].countryFlag)
}
else
{
cell?.textLabel?.text = cellDatas[indexPath.row].countryName
cell?.detailTextLabel?.text = cellDatas[indexPath.row].countryDialCode
cell?.imageView?.image = UIImage (named: cellDatas[indexPath.row].countryFlag)
}
cell?.textLabel?.textColor = UIColor.labelGray2()
cell?.detailTextLabel?.textColor = UIColor.labelGray2()
cell?.textLabel?.font = UIFont(name:"SF Pro Text", size:15)
cell?.detailTextLabel?.font = UIFont(name:"SF Pro Text", size:15)
return cell!
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.row, indexPath.section)
// let currentCell = tableView.cellForRow(at: indexPath)
if(isSearching)
{
print(filteredCellDatas[indexPath.row].countryFlag)
UserDefaults.standard.set(filteredCellDatas[indexPath.row].countryFlag, forKey: "preferredCountry")
if searchController.isActive {
DispatchQueue.main.async {
self.searchController.dismiss(animated: true, completion: {
self.performSegue(withIdentifier: "unWindFromSelectCountry", sender: nil)
})
}
} else {
// Play segue, dismiss or pop ...
self.performSegue(withIdentifier: "unWindFromSelectCountry", sender: nil)
}
}
else
{
print(cellDatas[indexPath.row].countryFlag)
UserDefaults.standard.set(cellDatas[indexPath.row].countryFlag, forKey: "preferredCountry")
self.performSegue(withIdentifier: "unWindFromSelectCountry", sender: nil)
}
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if section == 0 {
return CGFloat.leastNormalMagnitude
}
return tableView.sectionHeaderHeight
}
#objc func cancel(){
navigationController?.popViewController(animated: true)
}
#objc func refreshSelector()
{
if(!searchLoaded)
{
searchLoaded = true
self.tableView.tableHeaderView = searchController.searchBar
print( "Got ya")
}
refreshController.endRefreshing()
}
#objc func searchButtonAction() {
if(!searchLoaded)
{
searchLoaded = true
self.tableView.tableHeaderView = searchController.searchBar
// self.navigationItem.titleView = searchController.searchBar
}
self.searchController.searchBar.becomeFirstResponder()
self.searchController.searchBar.text = ""
// self.navigationItem.rightBarButtonItem = nil
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
As said the table view is of style grouped. I am configuring search bar from code. And updating results therefore using code in the same table view.
Thanks in advance
Maybe it is not a tableHeaderView I presume that it could be particular Section HeaderView try with this:
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if section == 0 {
return CGFloat.leastNormalMagnitude
}
return tableView.sectionHeaderHeight
}
Code is hiding header for first section in the table
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)
}
In iOS 9 I am using UISearchController and displaying its search bar within a UIViewController, I am experiencing a lot of lag the first time I click on the search bar and have tried everything i can think of to no avail...below is my code along with a link to a video of the lag happening - the lag happens on both the simulator and my device.
func setupUI() {
self.view.backgroundColor = UIColor.whiteColor()
// Required to properly display searchbar within nav & tabbar controllers
self.extendedLayoutIncludesOpaqueBars = true // have tried setting this to false as well
self.definesPresentationContext = true
self.searchResultsController = AppDelegate.getViewController(ScheduleStoryboard.name, controllerName: ScheduleStoryboard.Identifiers.foodSearchResults) as? SearchResultsController
self.searchController = UISearchController(searchResultsController: searchResultsController)
self.searchController.searchResultsUpdater = self
self.searchController.delegate = self
self.searchController.dimsBackgroundDuringPresentation = true
self.searchController.searchBar.delegate = self
self.searchController.searchBar.placeholder = "Search foods..."
self.searchController.searchBar.setBackgroundImage(UIImage(named: "background-searchbar")?.resizableImageWithCapInsets(UIEdgeInsetsMake(0, 0, 0, 0)), forBarPosition: .Any, barMetrics: .Default)
self.searchController.searchBar.tintColor = UIColor.whiteColor()
self.searchController.searchBar.sizeToFit()
// this headerView does NOT belong to the tableView, its anchored on top of the tableView so that the searchbar remains fixed when scrolling
self.headerView.addSubview(searchController.searchBar)
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.tableHeaderView?.backgroundColor = UIColor.clearColor()
self.tableView.tableHeaderView?.addBorder(.Bottom, color: UIColor.groupTableViewBackgroundColor(), width: 0.25)
self.segmentedControl.tintColor = UIColor.genioBlue()
}
Here is a link to the video showing whats happening: http://sendvid.com/xgq81stx
Thanks!
I've only ever created a search controller once, but I used a UITableViewController as my base class. Here is my implementation:
class SearchController: UITableViewController {
let searchController = UISearchController(searchResultsController: nil)
var items:[ArrayOfYourType]
var filteredItems:[ArrayOfYourType]
var scopeTitles:[String]?
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
searchController.searchBar.scopeButtonTitles = scopeTitles
searchController.searchBar.delegate = self
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchController.active {
return filteredItems.count
}
return items.count
}
func filterContentForSearchText(searchText: String, scope: String = "All") {
filteredItems = items.filter { item in
//return true or false depending on your filter
return true
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.Subtitle,
reuseIdentifier: nil)
let item: String
let category: String
if searchController.active {
item = filteredItems[indexPath.row].getTitle()
category = filteredItems[indexPath.row].getCategory()
}
else {
item = items[indexPath.row].getTitle()
category = items[indexPath.row].getCategory()
}
cell.textLabel?.text = item
cell.detailTextLabel?.text = category
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//your code here
}
}
//MARK: UISearchResultsUpdating
extension SearchController: UISearchResultsUpdating {
func updateSearchResultsForSearchController(searchController: UISearchController) {
if let _ = scopeTitles {
let searchBar = searchController.searchBar
let scope = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex]
filterContentForSearchText(searchController.searchBar.text!,scope:scope)
}
else {
filterContentForSearchText(searchController.searchBar.text!)
}
}
}
//MARK: UISearchBarDelegate
extension SearchController: UISearchBarDelegate {
func searchBar(searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
filterContentForSearchText(searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
}
}
I hope this helps :)
Could it be possible that the image you are using for the background search bar is too large?
It might be quicker to create a gradient. Here is a good tutorial