How can I connect the SearchBar to a different dataSource? - ios

I have a View that has a searchBar and a corresponding TableView below it and I have it working correctly. My question is; is it possible to hook up the SearchBar to a different TableView ? I am new to swift and iOS development and can not find a way to get it done . This image will help illustrate . AS you can see I have a Search Table View Controller and it is working correctly with the Search Bar. What I would like to do now is connect that SearchBar to the Second TableView instead . I would like to do that because eventually i'll have a TableView that will not be connected to the SearchBar, however when a user clicks on the SearchBar a new TableView will cover the original TableView . Below I will show my code for the working TableView, again I would like to connect the SearchBar to the Second
class SearchTableViewController: UITableViewController,UISearchBarDelegate {
#IBOutlet weak var searchBar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
struct ApplicationData {
var items: [String]
var filteredItems: [String] = []
init() {
items = ["John","Sam","Oliver"]
filterData(search: "")
}
mutating func filterData(search: String)
{
if search.characters.count>0 {
filteredItems = items.filter({ (item) in
let value1 = item.lowercased()
let value2 = search.lowercased()
let valid = value1.hasPrefix(value2)
return valid
})
}
filteredItems.sort(by: { (value1,value2) in value1 < value2 })
}
}
var AppData = ApplicationData()
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String){
if let text = searchBar.text {
let search = text.trimmingCharacters(in: .whitespaces)
AppData.filterData(search: search)
tableView.reloadData()
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return AppData.filteredItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ZipSearch", for: indexPath)
let data = AppData.filteredItems[indexPath.row]
cell.textLabel?.text = data
return cell
}
}
I am thinking that maybe the tableView.reloadData() piece of code is defaulting to the original tableView but any help would be greatly appreciated .

Related

UISearchBar not working or disabled after adding Tab Bar or Navigation Controller

I implemented a search bar to filter the result. It was working perfectly but then when I embedded this View Controller with a Navigation Controller, which is also associated with a Tab Bar Controller, the search bar did not respond when I clicked it. It also looks like it was disabled.
Here's how it looks
My storyboards
My code for the feed view controller:
import UIKit
import AlamofireImage
class RestaurantsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// Outlets
#IBOutlet weak var tableView: UITableView!
// Initiliazers
var restaurantsArray: [Restaurant] = []
// Add Search Bar Outlet + Variable for filtered Results
#IBOutlet weak var searchBar: UISearchBar!
var filteredRestaurants: [Restaurant] = []
override func viewDidLoad() {
super.viewDidLoad()
// Table View
tableView.delegate = self
tableView.dataSource = self
// Search Bar delegate
searchBar.delegate = self
definesPresentationContext = true
// Get Data from API
getAPIData()
}
// Update API results + restaurantsArray Variable + filteredRestaurants
func getAPIData() {
API.getRestaurants() { (restaurants) in
guard let restaurants = restaurants else {
return
}
self.restaurantsArray = restaurants
self.filteredRestaurants = restaurants
self.tableView.reloadData()
self.tableView.rowHeight = 150
}
}
}
// ––––– TableView Functionality –––––
extension RestaurantsViewController {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredRestaurants.count
}
// Configure cell to use [Movie] array instead of [[String:Any]] and Filtered Array
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Create Restaurant Cell
let cell = tableView.dequeueReusableCell(withIdentifier: "RestaurantCell") as! RestaurantCell
// Set cell's restaurant
cell.r = filteredRestaurants[indexPath.row]
return cell
}
// Send restaurant object to DetailViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let cell = sender as! UITableViewCell
if let indexPath = tableView.indexPath(for: cell) {
let r = filteredRestaurants[indexPath.row]
let detailViewController = segue.destination as! RestaurantDetailViewController
detailViewController.r = r
}
}
}
// Add protocol + Functionality for Searching
// UISearchResultsUpdating informs the class of text changes
// happening in the UISearchBar
extension RestaurantsViewController: UISearchBarDelegate {
// Search bar functionality
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText != "" {
filteredRestaurants = restaurantsArray.filter { (r: Restaurant) -> Bool in
return r.name.lowercased().contains(searchText.lowercased())
}
}
else {
filteredRestaurants = restaurantsArray
}
tableView.reloadData()
}
// Show Cancel button when typing
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
self.searchBar.showsCancelButton = true
}
// Logic for searchBar cancel button
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.showsCancelButton = false // remove cancel button
searchBar.text = "" // reset search text
searchBar.resignFirstResponder() // remove keyboard
filteredRestaurants = restaurantsArray // reset results to display
tableView.reloadData()
}
}
I did try putting definesPresentationContext in viewDidLoad() but it didn't make any difference for me. I think it's because either the tab bar controller or navigation controller that somehow "disabled" the search bar functionality.
This may help as you just ignore to input your view to navigationItem, which
can accept the event.
self.navigationItem.searchController.searchBar = searchBar

Search bar not working in Swift 4 IOS App

I have implemented a search bar to search for users, but nothing shows up in the table view when I search for something. I have attached a picture of my view controller below.
View Controller:
This view controller shows a list of all the users and the search bar is supposed to help the user find a username.
import UIKit
class FindFriendsViewController: UIViewController {
var users = [User]()
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
var searchItem = [String]()
var searching = false
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView()
tableView.rowHeight = 71
let tap = UITapGestureRecognizer(target: self.view, action: #selector(UIView.endEditing(_:)))
tap.cancelsTouchesInView = false
self.view.addGestureRecognizer(tap)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
UserService.usersExcludingCurrentUser { [unowned self] (users) in
self.users = users
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
extension FindFriendsViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return searchItem.count
} else {
return users.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FindFriendsCell") as! FindFriendsCell
// let user = users[indexPath.row]
var usernamesArr = [String]()
for user in users {
usernamesArr.append(user.username)
}
if searching {
cell.textLabel?.text = searchItem[indexPath.row]
} else {
cell.textLabel?.text = usernamesArr[indexPath.row]
cell.delegate = self
configure(cell: cell, atIndexPath: indexPath)
}
return cell
}
func configure(cell: FindFriendsCell, atIndexPath indexPath: IndexPath) {
let user = users[indexPath.row]
cell.usernameLabel.text = user.username
cell.followButton.isSelected = user.isFollowed
}
}
extension FindFriendsViewController: FindFriendsCellDelegate {
func didTapFollowButton(_ followButton: UIButton, on cell: FindFriendsCell) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
followButton.isUserInteractionEnabled = false
let followee = users[indexPath.row]
FollowService.setIsFollowing(!followee.isFollowed, fromCurrentUserTo: followee) { (success) in
defer {
followButton.isUserInteractionEnabled = true
}
guard success else { return }
followee.isFollowed = !followee.isFollowed
self.tableView.reloadRows(at: [indexPath], with: .none)
}
}
}
extension FindFriendsViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
var usernamesArr = [String]()
for user in users {
usernamesArr.append(user.username)
}
searchItem = usernamesArr.filter({$0.lowercased().prefix(searchText.count) == searchText.lowercased()})
searching = true
tableView.reloadData()
}
}
I am thinking about different problems that may occur in your code. You need to set the search bar delegate and the search Result Updater:
yourSearchController.searchBar.delegate = self
yourSearchController.searchResultsUpdater = self
If you don't have a controller, but directly the search bar:
yourSearchBar.delegate = self
yourSearchBar.searchResultsUpdater = self
And this as your delegate:
extension MasterViewController: UISearchBarDelegate {
// MARK: - UISearchBar Delegate
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
filterContentForSearchText(searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
}
}
extension MasterViewController: UISearchResultsUpdating {
// MARK: - UISearchResultsUpdating Delegate
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
let scope = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex]
filterContentForSearchText(searchController.searchBar.text!, scope: scope)
}
Or maybe you are missing something like updating. Check the data's path excluding the problem one for time. First, you enter the text inside the search bar, after that check in the code where the text goes and what happens. Did you update the table view when search bar is in end editing status?
If it doesn't help you, check this wonderful tutorial which I followed times ago: Search Bar iOS
try for search username using this method to filter
searchItem = users.filter({ (objUser) -> Bool in
return (objUser.username?.lowercased() ?? "").starts(with: searchBar.text!.trim().lowercased())
})
Hope this will help you.

how to Use a textfield as a "search bar" for a table view

I have a table view embedded into a regular view (as shown in this picture: Picture)
I have a search bar and working code so that when you start searching for a person's email, the table view automatically updates and just displays the people that match the search criteria.
Is there any way of using a textField in the first view as the search bar?
(if you look at the Picture, then the label that says "para" is where the user is going to type the email of their contact, can I use that label as the search bar, if so, how?)
in other words, how would I use "forField" as the search bar (forField is in the second bit of code)
here is the code for the tableView (currently working):
class UsersTableViewController: UITableViewController, UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
//update the search results
filterContent(searchText: self.searchController.searchBar.text!)
}
#IBOutlet var usersTableView: UITableView!
let searchController = UISearchController(searchResultsController: nil)
var usersArray = [NSDictionary?]()
var filteredUsers = [NSDictionary?]()
var databaseRef: DatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
databaseRef = Database.database().reference()
let usersRef = databaseRef.child("users")
let query = usersRef.queryOrdered(byChild: "email")
query.observe(.childAdded, with: {(snapshot) in
self.usersArray.append((snapshot.value as? NSDictionary?)!)
//insert the rows
self.usersTableView.insertRows(at: [IndexPath(row:self.usersArray.count-1, section: 0)], with: UITableView.RowAnimation.automatic)
}) { (error) in
print(error.localizedDescription)
}
print("HOLAAAAAAAAAAA")
print(self.usersArray)
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if ((searchController.isActive) && (searchController.searchBar.text != "")){
return filteredUsers.count
}
return self.usersArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let user: NSDictionary
if ((searchController.isActive) && (searchController.searchBar.text != "")){
user = filteredUsers[indexPath.row]!
}else{
user = self.usersArray[indexPath.row]!
}
cell.textLabel?.text = user["email"] as? String
cell.detailTextLabel?.text = user["name"] as? String
return cell
}
func filterContent(searchText: String){
self.filteredUsers = self.usersArray.filter({ user in
let userEmail = user!["email"] as? String
return(userEmail?.lowercased().contains(searchText.lowercased()))!
})
tableView.reloadData()
}
}
and for the view controller it is literally just the text fields and the labels.:
class PayViewController: UIViewController, STPAddCardViewControllerDelegate {
#IBOutlet weak var forField: UITextField!
#IBOutlet weak var toField: UITextField!
#IBOutlet weak var amountLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
}
in other words, how would I use "forField" as the search bar
After recent comments and updates it, I added the following to payViewController:
override func viewDidLoad() {
super.viewDidLoad()
//var myTable:UsersTableViewController?
myTable = self.children[0] as! UsersTableViewController
self.toField.addTarget(self, action: #selector(UsersTableViewController.textChanges(_:)), for: UIControl.Event.editingChanged)
}
and changed the following functions in the table view controller:
func updateSearchResults(for searchController: UISearchController) {
//update the search results
filterContent(searchText: searchT)
}
#objc func textChanges(_ textField: UITextField) {
let text = textField.text! // your desired text here
// Now do whatever you want.
searchT = text
}
Your problem is accessing the child from the parent so you need to declare this inside PayViewController
var myTable:UsersTableViewController?
and inside viewDidLoad
myTable = self.children[0] as! UsersTableViewController
after that in chnage action of the textfeilds do
myTable.filterContent(searchText:forField.text!)
Btw you can also transfer the search bar related content inside the searchController or to remove that child segue and add a table directly below the fields and make everything inside PayViewController
You don't have to use UITableViewController for utilizing table an instance of UITableView is sufficient and can be directly as a subview inside the vc

How to update the data from the searchResultsController (UISearchController)

So I am using a searchResultsController, which takes an array of Strings, and shows them in a tableview (It's an autocomplete list). When the user presses the 'Search' button on the keyboard, and the entered String is not yet in my Tableview, I want to add it, and update the tableview accordingly.
The issue is that once I added a String to the array, and make a new search, the array isn't updated with the new value!
Here is my code:
In my ViewDidLoad() on the Overview.swift class
class Overview: UIViewController,UISearchControllerDelegate,UISearchBarDelegate,UICollectionViewDelegate,UICollectionViewDataSource {
var mySearchController : UISearchController!
var mySearchBar : UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
let src = SearchResultsController(data: convertObjectsToArray())
// instantiate a search controller and keep it alive
mySearchController = UISearchController(searchResultsController: src)
mySearchController.searchResultsUpdater = src
mySearchBar = mySearchController.searchBar
//set delegates
mySearchBar.delegate = self
mySearchController.delegate = self
}
This is the data function, used for the UISearchController
func convertObjectsToArray() -> [String] {
//open realm and map al the objects
let realm = try! Realm()
let getAutoCompleteItems = realm.objects(AutoComplete).map({$0})
...
return convertArrayStrings // returns [String] with all words
}
So when the user pressed the search button on the keyboard, I save that word to my database.
Now I need to put the updated version of convertObjectsToArray() in my searchResultsController, but I haven't found out how to do this. All help is welcome
And last, but not least, my SearchResultsController class, which is used in the viewDidLoad of my Overview.swift class.
class SearchResultsController : UITableViewController {
var originalData : [String]
var filteredData = [String]()
init(data:[String]) {
self.originalData = data
super.init(nibName: nil, bundle: nil)
}
required init(coder: NSCoder) {
fatalError("NSCoding not supported")
}
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.filteredData.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.textLabel!.text = self.filteredData[indexPath.row]
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
clickedInfo = filteredData[indexPath.row]
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
}
For the filtering of my words in the tableview (when user types something, only matching Strings are shown), I use the following extension.
extension SearchResultsController : UISearchResultsUpdating {
func updateSearchResultsForSearchController(searchController: UISearchController) {
let sb = searchController.searchBar
let target = sb.text!
self.filteredData = self.originalData.filter {
s in
let options = NSStringCompareOptions.CaseInsensitiveSearch
let found = s.rangeOfString(target, options: options)
return (found != nil)
}
self.tableView.reloadData()
}
You can use the search controller's update function for that I think:
func updateSearchResultsForSearchController(searchController: UISearchController) {
convertObjectsToArray()
self.tableView.reloadData()
}

UITextField and UITableView on a single view controller

I'm trying to make a view controller that has one text field that populates the tableview below, ideally the user will be able to continue to add to the tableview without jumping between two views.
I previously had it working with the text field on one view that populates a UITableView and used prepareForSegue to push the data to the table, but I haven't been able to get it to work with just one view.
Can anyone please point out where I'm going wrong or push me to a tutorial / documentation to help?
Edit: Clarity
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {
#IBOutlet var tableView: UITableView!
#IBOutlet weak var textField: UITextField!
var items: [String] = ["Pls", "work", "pls", "work", "pls"]
var foodGroup: FoodGroup = FoodGroup(itemName:"")
//var foodGroup: [FoodGroup] = []
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
cell.textLabel.text = self.items[indexPath.row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("Selected cell #\(indexPath)")
}
func addFood(sender: AnyObject!) {
if (countElements(self.textField.text) > 0) {
self.foodGroup = FoodGroup(itemName: self.textField.text)
}
}
#IBAction func addFoodToList() {
let source = FoodGroup
let foodGroup:FoodGroup = source.foodGroup
if foodGroup.itemName != "" {
self.foodGroup.append(foodGroup)
self.tableView.reloadData()
}
}
}
It seems like your intention here is to have your dataSource be an array of FoodGroup objects. If this is indeed the case you can get rid of your foodGroup instance variable and update your items definition to be like so:
var items = [FoodGroup]()
then in addFoodToList:
if self.textField.text != "" {
let foodGroup = FoodGroup(itemName: self.textField.text)
self.items.append(foodGroup)
self.tableView.reloadData()
}
and finally in cellForRowAtIndexPath:
var cell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
let foodGroup = self.items[indexPath.row] as FoodGroup
cell.textLabel.text = foodGroup.itemName
return cell
Also I don't quite see the intention of your the addFood(sender: AnyObject!) function. Looks like cruft. I would get rid of it. Good luck!

Resources