I am using a UISearchController in my project. I initiate the search controller by supplying the init(searchResultsController) method with an UIViewController object that manages a tableView. The code for this object is:
import UIKit
class ResultsTableViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var list: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension ResultsTableViewController: UITableViewDelegate{}
extension ResultsTableViewController: UITableViewDataSource{
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell")
if cell == nil {
cell = UITableViewCell(style: .Default, reuseIdentifier: "Cell")
cell?.textLabel?.text = list[indexPath.row]
} else {
cell?.textLabel!.text = list[indexPath.row]
}
return cell!
}
}
Furthermore, when I try to access the resultsTableViewController.tableView from the updateSearchResultsForSearchController(searchController: UISearchController) method of the UISearchResultsUpdating protocol to populate its "list" array, it gives me an error. The tableView returns nil and the app crashes. I would like to point out that I have connected the data source, delegate, and the IBOutlet variable of the tableView to the appropriate view controller. I was hoping for someone to explain to me why this happens? I think I have a misunderstanding in the life cycle of the ResultsTableViewController. Lastly, when I drag a TableViewController from the storyboard instead of making my own table view controller from scratch everything works smoothly without any errors! Can you please help me understand whats going on here?
Edit: The code for my initial view controller is:
import UIKit
class FirstViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var resultsTableViewController = ResultsTableViewController()
var searchController: UISearchController!
let list = ["Apple", "Orange", "Bananas","Kiwi", "Cucumbers", "Apricot","Peach", "Cherry", "Mangosteen","Strawberry", "Blueberry", "Raspberry","Watermelon", "Persimmon", "plums","Papaya", "Jackfruit", "Lichi"]
var filteredList: [String]!
override func viewDidLoad() {
super.viewDidLoad()
setUpSearchController()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func setUpSearchController(){
searchController = UISearchController(searchResultsController: resultsTableViewController)
searchController.dimsBackgroundDuringPresentation = false
searchController.searchResultsUpdater = self
searchController.hidesNavigationBarDuringPresentation = true
tableView.tableHeaderView = searchController.searchBar
tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0), atScrollPosition: UITableViewScrollPosition.Top, animated: false)
}
}
extension FirstViewController: UITableViewDelegate, UITableViewDataSource{
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell")
if cell == nil {
cell = UITableViewCell(style: .Default, reuseIdentifier: "cell")
cell?.textLabel?.text = list[indexPath.row]
}else {
cell?.textLabel?.text = list[indexPath.row]
}
return cell!
}
}
extension FirstViewController: UISearchResultsUpdating{
func updateSearchResultsForSearchController(searchController: UISearchController) {
filteredList = list.filter({
item in
return item.containsString(searchController.searchBar.text!)
})
resultsTableViewController.list = filteredList
resultsTableViewController.tableView.reloadData()
}
}
var resultsTableViewController = ResultsTableViewController()
creates a new ResultsTableViewController but it isn't linked to your storyboard scene, so none of the #IBOutlets will be set. You need to set an identifier for your scene (say resultsViewController) and then use that to instantiate the view controller from the storyboard:
var resultsTableViewController: ResultsTableViewController!
override func viewDidLoad() {
super.viewDidLoad()
setUpSearchController()
}
func setUpSearchController(){
let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
self.resultsTableViewController = storyboard.instantiateViewControllerWithIdentifer("resultsViewController") as! ResultsTableViewController
searchController = UISearchController(searchResultsController: resultsTableViewController)
searchController.dimsBackgroundDuringPresentation = false
searchController.searchResultsUpdater = self
searchController.hidesNavigationBarDuringPresentation = true
tableView.tableHeaderView = searchController.searchBar
tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0), atScrollPosition: UITableViewScrollPosition.Top, animated: false)
}
Related
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
It is a question.
This class is not key value code-compliant for the key error is given.
However, I am not sure which part is wrong. This error appears at cellForRowAt.
Thank you for your answer.
If you want it, I can also show the code of ContentTableViewCell, so please call out.
import UIKit
class SearchViewController: UIViewController, UISearchResultsUpdating {
#IBOutlet weak var tableview: UITableView!
var songs = [
"裁量権を持って、若者向けカレンダーアプリ開発をしたいiOSエンジニア募集!",
"自信を持って、応援出来るエンジニアを募集!"
]
var searchSongs = [String]()
func updateSearchResults(for searchController: UISearchController) {
let searchString = searchController.searchBar.text!
searchSongs = songs.filter {(name) -> Bool in
return name.contains(searchString)
}
tableview.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.prefersLargeTitles = true
let searchController = UISearchController(searchResultsController:nil)
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
navigationItem.searchController = searchController
definesPresentationContext = true
delegate()
}
}
extension searchViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if navigationItem.searchController?.isActive == true {
return searchSongs.count
} else {
return songs.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableview.dequeueReusableCell(withIdentifier: "ContentTableViewCell", for: indexPath) as! ContentTableViewCell
if navigationItem.searchController?.isActive == true {
cell.commonInit(content: searchSongs[indexPath.row])
} else {
cell.commonInit(content: songs[indexPath.row])
}
return cell
}
func delegate() {
tableview.delegate = self
tableview.dataSource = self
let nibName = UINib(nibName: "ContentTableViewCell", bundle: nil)
self.tableview.register(nibName, forCellReuseIdentifier: "ContentTableViewCell")
}
}
Check in your Storyboard. I think any outlet is not connected properly.
Check is there any yellow warning triangle in your storyboard on which screen this error is coming. If you found any, remove and connect the outlet again.
I am trying to connect a storyboard which I made to my view controller file in my chat app.
What I did is...
1) Create a view controller file(swift).
2) Create a storyboard file and set view controller class as a custom class.
3) Connect my component(table view) in my storyboard to a view controller as an outlet.
Now, I got "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value" error message.
Code is the following.
import UIKit
class ChatViewController: UIViewController {
var _device_width: Int = 0
var _device_height: Int = 0
var _footer_size: Int = 0
var _backBtn: UIBarButtonItem!
var window: UIWindow?
var _toolBar: UIToolbar!
var bottomView: ChatRoomInputView!
var chats: [ChatEntity] = []
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
showNavigateBar()
setupUI()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override var canBecomeFirstResponder: Bool {
return true
}
override var inputAccessoryView: UIView? {
return bottomView
}
func showNavigateBar() {
let backButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
navigationItem.backBarButtonItem = backButtonItem
self.title = 'My App'
}
}
extension ChatViewController {
func setupUI() {
self.view.backgroundColor = UIColor(red: 113/255, green: 148/255, blue: 194/255, alpha: 1)
// ERROR happen on this line.
// "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value"
tableView.separatorColor = UIColor.clear
tableView.estimatedRowHeight = 10000
tableView.rowHeight = UITableViewAutomaticDimension
tableView.allowsSelection = false
tableView.keyboardDismissMode = .interactive
tableView.register(UINib(nibName: "YourChatViewCell", bundle: nil), forCellReuseIdentifier: "YourChat")
tableView.register(UINib(nibName: "MyChatViewCell", bundle: nil), forCellReuseIdentifier: "MyChat")
let chat1 = ChatEntity(text: "text1", time: "10:01", userType: .I)
let chat2 = ChatEntity(text: "text2", time: "10:02", userType: .You)
let chat3 = ChatEntity(text: "text3", time: "10:03", userType: .I)
chats = [chat1, chat2, chat3]
}
}
extension ChatViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.chats.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let chat = self.chats[indexPath.row]
if chat.isMyChat() {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyChat") as! MyChatViewCell
cell.clipsToBounds = true
// Todo: isRead
cell.updateCell(text: chat.text, time: chat.time, isRead: true)
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "YourChat") as! YourChatViewCell
cell.clipsToBounds = true
cell.updateCell(text: chat.text, time: chat.time)
return cell
}
}
}
extension ChatViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath)
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 10
}
}
And I defined view controller as a custom class.
What should I do to work correct?
Please give me an advice.
ADD)
ChatViewController is called by another class file.
let chatViewController = ChatViewController()
self.navigationController?.pushViewController(chatViewController, animated: false)
And I believe #IBOutlet is connect correctly.
ChatViewController.swift
ChatViewController.storyboard
And I know the tableView is nil when this function was called.
Actually, I don't know much about the storyboard because I usually don't use storyboard. I prefer to create UI by the code. But now, I have to use storyboard file because I want to use a sample project of the chat system.
ADD2)
I think I may mistake the setting of the storyboard. I have no idea how to use navigation controller or entry point on the storyboard.
You should add delegate and datasource in your viewLoad() function.
override func viewDidLoad() {
super.viewDidLoad()
showNavigateBar()
tableView.delegate = self
tableView.dataSource = self
setupUI()
}
If error still exist after that, check your "Outlets" in your Storyboard.
I fixed the problem.
I modified call method.
let storyBoard : UIStoryboard = UIStoryboard(name: "ChatViewController", bundle:nil)
let chatViewController = storyBoard.instantiateViewController(withIdentifier: "ChatViewController")
self.navigationController?.pushViewController(chatViewController, animated: true)
Thank you for your kindness advice.
theres any way to hide the cancel button on UISearchController?
The other behaviour i would like to know if its possible, is when the user press the Cancel Button, to set the text on the UISearchBar.
Thanks
Using seachController.searchBar.setShowsCancelButton(false, animated:false) still seems to not do it for this.
Try adding the UISearchBarDelegate to your controller and assign the searchBar's delegate to self. Then you can use the searchBarShouldBeginEditing to edit its visibility.
import UIKit
class TableViewController: UITableViewController, UISearchBarDelegate {
var searchController: UISearchController!
let menuItems: [String] = ["Row 1", "Row 2", "Row 3"]
override func viewDidLoad() {
super.viewDidLoad()
let searchResultsController = storyboard!.instantiateViewControllerWithIdentifier("SearchResultsViewControllerStoryboardIdentifier") as! SearchResultsTableViewController
searchController = UISearchController(searchResultsController: searchResultsController)
searchController.hidesNavigationBarDuringPresentation = false
searchController.searchBar.delegate = self
}
#IBAction func searchButtonPressed(sender: UIBarButtonItem) {
presentViewController(searchController, animated: true, completion: nil)
}
func searchBarShouldBeginEditing(searchBar: UISearchBar) -> Bool {
searchController.searchBar.setShowsCancelButton(false, animated: true)
return true
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return menuItems.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = self.menuItems[indexPath.row]
return cell
}
}
I have a simple tableView, DailyAttendanceController, with a searchbar. Searchbar was added in its viewDidLoad. View TableView here.
import UIKit
class OldCellsTableViewController: UITableViewController {
let data = ["A", "B", "C"]
var searchController:UISearchController!
let resultsController = SearchResultsUpdater()
override func viewDidLoad() {
super.viewDidLoad()
resultsController.data = data
searchController = UISearchController(searchResultsController: resultsController)
let searchBar = self.searchController.searchBar
searchBar.scopeButtonTitles = ["All", "Short", "Long"]
searchBar.placeholder = "Search for a student"
searchBar.sizeToFit()
self.tableView.tableHeaderView = searchBar
self.searchController.searchResultsUpdater = resultsController
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let info = data[indexPath.row]
cell.textLabel?.text = info
return cell
}
}
I then created another class, SearchResultsController, to handle the search. Searching works fine, but space is allocated for DailyAttendanceController's header. View SearchResultsController here
How do I remove this?
Code for my SearchResultsController:
import UIKit
import CoreData
class SearchResultsController: UITableViewController, UISearchResultsUpdating {
var data:[String] = []
var filteredData:[String] = []
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredData.count
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
filteredData.removeAll(keepCapacity: true)
let searchString = searchController.searchBar.text
for info in data {
if (info.uppercaseString.rangeOfString(searchString.uppercaseString) != nil) {
filteredData.append(info)
}
}
tableView.reloadData()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = filteredData[indexPath.row]
return cell
}
}