There is a search controller with two scopes of decks and cards. I set up a search for these two models, but now the task is that when I tap on these results, for decks should be one segue for cards another. In the pictures, you can see, after I typed search text, it shows results correctly, but when you tap on the result row, nothing happens. Can't find any solution for almost a week...
class DecksTableViewController: UITableViewController {
private var searchController: UISearchController!
private var decks: Results<Deck>!
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView()
let resultsTableController = self.storyboard?.instantiateViewController(withIdentifier: "ResultsTableViewController") as! ResultsTableViewController
configureSearchController(resultsTableController)
definesPresentationContext = true
navigationItem.hidesSearchBarWhenScrolling = true
navigationController?.navigationBar.prefersLargeTitles = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadDecks()
}
private func loadDecks() {
decks = StorageManager.realm.objects(Deck.self).sorted(byKeyPath: "dateCreated", ascending: true)
tableView.reloadData()
}
private func configureSearchController(_ searchResultsController: ResultsTableViewController) {
searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = searchResultsController
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search"
searchController.searchBar.scopeButtonTitles = ["Decks", "Cards"]
searchController.searchBar.delegate = searchResultsController
navigationItem.searchController = searchController
}
}
Here the search results controller:
import UIKit
import RealmSwift
class ResultsTableViewController: UITableViewController {
var filteredDecks: Results<Deck>?
var filteredCards: Results<Card>?
var scope: String?
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.tableFooterView = UIView()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if scope == "Decks" {
return filteredDecks?.count ?? 0
} else {
return filteredCards?.count ?? 0
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "resultsCell", for: indexPath)
var content = cell.defaultContentConfiguration()
if scope == "Decks" {
let deck = filteredDecks?[indexPath.row]
content.text = deck?.name
} else {
let card = filteredCards?[indexPath.row]
content.text = card?.front
content.secondaryText = card?.back
}
cell.contentConfiguration = content
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if scope == "Decks" {
guard let cardsVC = self.storyboard?.instantiateViewController(identifier: "cardsVC")
as? CardsTableViewController
else {
return
}
cardsVC.selectedDeck = filteredDecks?[indexPath.row]
self.parent?.navigationController?.pushViewController(cardsVC, animated: true)
} else {
guard let cardDetailVC = self.storyboard?.instantiateViewController(identifier: "cardDetailVC")
as? NewCardViewController
else {
return
}
cardDetailVC.selectedCard = filteredCards?[indexPath.row]
self.navigationController?.pushViewController(cardDetailVC, animated: true)
}
tableView.deselectRow(at: indexPath, animated: true)
}
}
extension ResultsTableViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
let scope = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex]
filterContentForSearchText(searchController.searchBar.text!, scope: scope)
}
private func filterContentForSearchText(_ searchText: String, scope: String) {
var decks: Results<Deck>?
var cards: Results<Card>?
if scope == "Cards" {
cards = StorageManager.realm.objects(Card.self).sorted(byKeyPath: "front")
filteredCards = cards?.filter("front CONTAINS[c] '\(searchText)'")
} else {
decks = StorageManager.realm.objects(Deck.self).sorted(byKeyPath: "name")
filteredDecks = decks?.filter("name CONTAINS[c] '\(searchText)'")
}
self.scope = scope
tableView.reloadData()
}
}
extension ResultsTableViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
filterContentForSearchText(searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
}
}
Thanks for additional information.
You notice that text is not formatted in comments, so I reformat here:
Where is result row (a cell in which table) ? I suppose it is in the table of ResultsTableViewController ? Do I understand correctly that this table can contain either Decks or cards ?
- Yep, right, results are cells in ResultsTableViewController, there might be decks or cards, depending on scope.
Is behaviour different for decks or cards, or is it the same "nothing happens" ?
- Yes, for both nothing happens.
Did you check identifiers are effectively "cardsVC" and "cardDetailVC" (with same upper/lowercase) -
Yes, I checked. This is the output:
navigation controller is nil, I also tried to embed ResultsTableViewController with navigation controller, but it didn't help.
print("didSelect scope", scope, "indexPath", indexPath)
- didSelect scope Optional("Decks") indexPath [0, 0]
print("cardsVC instantiated, with selectedDeck", cardsVC.selectedDeck)
print("self.parent?.navigationController", self.parent?.navigationController)
- Decks cardsVC instantiated, with selectedDeck Optional(Deck { _id = 60e98c5da7d8064df3298f68; name = Greetings; dateCreated = 2021-07-10 12:02:37 +0000; layout = frontToBack; autoplay = 0; cards = List <0x283afcfc0> ( [0] Card { _id = 60e98c63a7d8064df3298f6c; front = hello; back = ; dateCreated = 2021-07-10 12:02:43 +0000; audioName = (null); }, [1] Card { _id = 60e98c6aa7d8064df3298f6d; front = good evening; back = ; dateCreated = 2021-07-10 12:02:50 +0000; audioName = (null); } ); })
- self.parent?.navigationController nil
- didSelect scope Optional("Cards") indexPath [0, 0] Cards cardDetailVC instantiated, with selectedCard Optional(Card { _id = 60e98c6aa7d8064df3298f6d; front = good evening; back = ; dateCreated = 2021-07-10 12:02:50 +0000; audioName = (null); })
- self.navigationController nil
Now the cause: ResultsTableViewController is not part of the navigation stack, hence its navigationController is nil. In fact, you instantiate directly:
let resultsTableController = self.storyboard?.instantiateViewController(withIdentifier: "ResultsTableViewController") as! ResultsTableViewController
You could try to add a property in
class ResultsTableViewController: UITableViewController {
var originatingController: DecksTableViewController?
var filteredDecks: Results<Deck>?
and set it when you create resultsTableController
class DecksTableViewController: UITableViewController {
private var searchController: UISearchController!
private var decks: Results<Deck>!
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView()
let resultsTableController = self.storyboard?.instantiateViewController(withIdentifier: "ResultsTableViewController") as! ResultsTableViewController
resultsTableController?.originatingController = self
configureSearchController(resultsTableController)
Then call it as:
self.originatingController?.navigationController?.pushViewController(cardsVC, animated: true)
I could not test, but that should be the logic (may need some tuning though).