UISearchController/SearchBar in Navigation Controller - swift

I have a TableView with simple data in it. I have added SearchBar to the Navigation controller by code. (Not sure have i done proper way or not, but it is working). This is how it looks:
Code is below:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var data = ["Hello","This","Is","Table","View","With","Search","Bar","Included","In","NavBar","But","SearchBar","Is","Not","Done","Properly"]
var filtered = [String]()
override func viewDidLoad() {
super.viewDidLoad()
//My original array and filtered arrays.
filtered = data
// Here a play with preferences add/delete
let searchBar = UISearchController(searchResultsController: nil)
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.searchController = searchBar
navigationItem.searchController?.dimsBackgroundDuringPresentation = false
navigationItem.searchController?.searchResultsUpdater = self
navigationItem.hidesSearchBarWhenScrolling = false
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filtered.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = filtered[indexPath.row]
return cell
}
}
extension ViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
if searchController.searchBar.text! == "" {
filtered = data
}
else {
filtered = data.filter {$0.lowercased().contains(searchController.searchBar.text!.lowercased())}
}
self.tableView.reloadData()
}
}
As you see i have done it by creating SearchController and then adding it to a NavController.
Also implemented delegate UISearchResultsUpdating method to search and filter.
I have tried several ways but i do not know how to access to properties of SearchBar and NavController. (not by storyboard)
Some screenshots:
After like a day of search and try, i am not actually sure about it, as some methods work but some not. As example:
This works:
navigationItem.searchController?.searchBar.tintColor = .white
This does not work:
navigationItem.searchController?.searchBar.barTintColor = .red
So why can i access to one property and cannot to other?
It is a very simple functionality, but tricky to find.
Maybe you could provide me some resource i could use for my case, as indicated in Images?
Or if you have a time - some solution, maybe root example.
Thanks in advance.
Will continue to search.

Related

Configure in TableView is not being reached

So I am trying to have a TableView displayed, but I'm currently only getting an empty tableview. Upon further inspection, I see that the configure block is not being run. Why is this?
Code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: teamCellIdentifier, for: indexPath)
print("reached")
func configure(cell: UITableViewCell,
for indexPath: IndexPath) {
print("not reached")
guard let cell = cell as? TeamCell else {
return
}
let team = fetchedResultsController.object(at: indexPath)
cell.teamLabel.text = team.teamName
cell.scoreLabel.text = "Wins: \(team.wins)"
if let imageName = team.imageName {
cell.flagImageView.image = UIImage(named: imageName)
} else {
cell.flagImageView.image = nil
}
}
return cell
}
}
TeamCell
class TeamCell: UITableViewCell {
// MARK: - IBOutlets
#IBOutlet weak var teamLabel: UILabel!
#IBOutlet weak var scoreLabel: UILabel!
#IBOutlet weak var flagImageView: UIImageView!
// MARK: - View Life Cycle
override func prepareForReuse() {
super.prepareForReuse()
teamLabel.text = nil
scoreLabel.text = nil
flagImageView.image = nil
}
}
The reason why your tableView is empty or it looks like empty and your tableView methods are not being called are two reasons.
1)- Table view heigh or width is equal to 0 and there is no need to load and display tableView, respectively tableView cell.
2)- You did not connect tableView data source and delegates to your view controller.
2.1) You can add TableView delegates and data sources through storyboard (see image down below)
2.2) You can add TableView delegates and data sources thrugh code in your view controller, like:
2.2.1) In viewDidLoad() method:
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// TableView Delegates & DataSource
tableView.delegate = self
tableView.dataSource = self
}
2.2.2) Or view tableView outlet didSet (I prefer this way, if I don't do it via storyboard):
#IBOutlet weak var tableView: UITableView! {
didSet {
tableView.delegate = self
tableView.dataSource = self
}
}
Your code has a structure like this:
// This next line defines a function called a
func a() {
print("a")
// This next line only defines a function called b:
// that is scoped inside the 'a' function
func b() {
print("b")
}
// Defining a function does not invoke the function
// That would need an actual call here like this:
// b()
}
// I can't call b here because it's nested inside the function 'a'
First you have a nested func inside function?
Take it out....
then after this line :
let cell = tableView.dequeueReusableCell(withIdentifier: teamCellIdentifier, for: indexPath)
do this:
configure(cell: cell,for indexPath: indexPath)

CellForRowAtIndexPath not Called when reloading table data

I've worked through a lot of answers like this one but I'm still stuck.
I have a viewcontroller with a collectionView and a searchController. When the user searches I want to display a UITableView on top of the collectionView. I'm trying to add the tableview programmatically b/c it's a simple text label I'm displaying
I am able to display the search results for the dummy array I have in viewdidload of my tableviewcontroller class. However when I get the actual results, I can only print them in the numberOfRows function.
All help would be greatly appreciated, here's my code:
This is my TableView Class:
import UIKit
class HashtagSearchList: UITableViewController {
var hashtagListTableview = UITableView()
var hashtagList = [String]()
override func viewDidLoad() {
super.viewDidLoad()
hashtagList = ["One", "Two", "Three"]
hashtagListTableview.delegate = self
hashtagListTableview.dataSource = self
}
// MARK: - Table view data source
//I don't need this, but added it based on some answers... didn't help
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//All these print statements show correct values
print("List Count = \(hashtagList.count)")
print("List of Strings: \(hashtagList)")
print("frame size = \(tableView.frame)")
return hashtagList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
//This print statement only fires on ViewdidLoad
print("cellForRowAt = \(hashtagList[indexPath.row])")
cell.textLabel?.text = hashtagList[indexPath.row]
cell.backgroundColor = .red
return cell
}
}
Here is my ViewController / SearchController code:
class ExploreVC: UIViewController {
var hashtagSearchController = HashtagSearchList()
override func viewDidLoad() {
super.viewDidLoad()
//Search Controller
navigationItem.searchController = searchController
searchController = UISearchController(searchResultsController: hashtagSearchController)
}
//......
// SEARCH CONTROLLER
extension ExploreVC: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
let searchText = searchController.searchBar.text
if searchText == "" { return }
PostFirebase.getHashtagList(hashtag: searchText) { (hashtagList) in
self.hashtagSearchController.hashtagListTableview.frame = CGRect(x: 0.0, y: 0.0, width: self.view.bounds.width, height: self.view.bounds.height)
self.hashtagSearchController.hashtagList = hashtagList
//self.hashtagSearchController.hashtagListTableview.reloadData()
DispatchQueue.main.async {
self.hashtagSearchController.hashtagListTableview.reloadData()
}
}
}
}
}
Here is the tableview from viewDidLoad, and it never changes from here
You initialized hashtagListTableview in HashtagSearchList but you didn't add layout constraints. By default, it will have .zero frame and won't be displayed on the screen.
I guess that the table view on the screen is tableView from UITableViewController. That's why nothing happened when you call self.hashtagSearchController.hashtagListTableview.reloadData().
To fix it, try to use tableView instead of hashtagListTableview. Replace
self.hashtagSearchController.hashtagListTableview.reloadData()
with
self.hashtagSearchController.tableView.reloadData()

My TableView's not changed after reload data

I wrote a class that let me create multiple tableView simply. When I call this class for the first time, everything work well. But when I change some data, and reload the table, nothing changed.
Sample code:
class TestViewController: UIViewController {
var arrData = ["a","b","c"]
var myTableView: MyTableView?
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
myTableView = MyTableView(table: tableView, data: arrData)
}
#IBAction func buttonTapped(_ sender: UIButton) {
arrData = ["d","e","f"]
myTableView!.tableView.reloadData() //=> Not change anything
}
}
class MyTableView: NSObject, UITableViewDataSource {
var tableView: UITableView
var data: Array<String>
init(table: UITableView, data: Array<String>) {
self.data = data
self.tableView = table
super.init()
self.tableView.dataSource = self
self.tableView.register(MyTableViewCell.self, forCellReuseIdentifier: "myCell")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.data.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
cell.textLabel!.text = self.data[indexPath.row]
return cell
}
}
class MyTableViewCell : UITableViewCell {
//something here
}
When the view was loaded, the table has 3 rows: a,b,c. When I tap the button, nothing changed (expected: d,e,f)
Please help me!
Swift arrays are copied by value so the line self.data = data will take a copy of your array. Later changing the array contents of the source will not be reflected in the copy in your MyTableView.
You'll need to pass the array over again and take a second copy to update the table, e.g. write a method in MyTableView similar to the following:-
func setNewValues(data: Array<String>)
{
self.data = data
self.tableView.reloadData()
}
and call that from your buttonTapped function, i.e.:
#IBAction func buttonTapped(_ sender: UIButton) {
arrData = ["d","e","f"]
myTableView!.setNewValues(data: arrData)
}
Be careful with the force-unwrapped myTableView though - I'd replace that '!' with '?'.

How to refresh tableview when user inserts an item into the database?

When the user inserts an item into the database and then clicks the back button to return to the tableview, the tableview is not displaying the new item. I have to stop the app, and run it again for the new item to be displayed. My database is working perfectly fine, so i don't think thats the problem.
I do reload the database again in the mainController inside ViewDidLoad(). I also tried doing tableView.reloadData() inside ViewDidLoad() as well and that doesn't do anything.
class MainViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView?
let mainDelegate = UIApplication.shared.delegate as! AppDelegate
override func viewDidLoad() {
super.viewDidLoad()
// read from the database
mainDelegate.readDataFromDatabase()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return mainDelegate.people.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BookTableViewCell") as! BookTableViewCell
let rowNum = indexPath.row
cell.lblTitle?.text = mainDelegate.people[rowNum].title
cell.accessoryType = .disclosureIndicator
return cell
}
class NewBookItemViewController: UIViewController, UITextFieldDelegate, UINavigationControllerDelegate {
#IBOutlet weak var titletxt : UITextField!
#IBAction func buttonSave(sender: UIButton){
// step 18b - instantiate Data object and add textfield data
let person : Data = Data.init()
person.initWithData(theRow: 0, thetitle: title)
let mainDelegate = UIApplication.shared.delegate as! AppDelegate
// step 18c - do the insert into db
let returnCode : Bool = mainDelegate.insertIntoDatabase(person: person)
}
You need
override func viewWillAppear(_ animated:Bool) {
super.viewWillAppear(animated)
mainDelegate.readDataFromDatabase()
tableView.reloadData()
}
As viewDidLoad is called once when the vc is loaded , also it's not the responsiliblity of the appDelegate to do such things , conside having a dataSource class that handle read/write from the database

UITableView Custom Class not showing results

I have a ViewController on the storyboard.
TestViewController.swift
class TestViewController: UIViewController {
var users : Dictionary = [String:User]()
let customTableView : UITableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
customTableView.register(UITableViewCell.self, forCellReuseIdentifier: "userCell")
let customTable = CustomTable(users:users)
customTableView.delegate = customTable
customTableView.dataSource = customTable
view.addSubview(customTableView)
customTableView.translatesAutoresizingMaskIntoConstraints = false
customTableView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
customTableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
customTableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
customTableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
}
}
CustomTable.swift
class CustomTable: NSObject, UITableViewDelegate, UITableViewDataSource {
var users : Dictionary = [String:User]()
var userNames = ["Steve", "Joe", "Bob"]
var userIds = [String]()
init(users: [String:User]) {
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("HEY")
return userNames.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("HIHI")
let cell = tableView.dequeueReusableCell(withIdentifier: "userCell", for: indexPath)
let text = userNames[indexPath.row]
cell.textLabel?.text = text
return cell
}
}
numberOfRowsInSection - seems to fire
cellForRowAt - doesn't fire / print "HIHI", therefore, my table is empty.
I have moved creating the table to viewDidAppear, nothing.
I have searched tirelessly to find similar situations, but nothing seems to solve it. Does anything stick out to anyone or have done this successfully?
Declare customTable reference at class level. It becomes nil as soon as it leaves the scope of viewDidLoad method.