I have master-detail application, where I use UISearchController to filter cells in UITableViewController (master). I have problem with disappearing UISearchBar after segue. Steps to reproduce problem:
Run app in portrait mode
Touch SearchBar and type search query
Touch one of the cells in TableView and segue to detail controller
While in detail controller, change orientation of the device to landscape mode
Segue back to master. Now SearchBar is partially hidden behind Navigation Bar, even after chaning orientation back to portrait.
My Master controller's MWE:
import UIKit
class MasterViewController: UITableViewController, UISearchResultsUpdating {
var data = ["First", "Second", "Third", "Fourth"]
var searchController: UISearchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
configureSearchController()
}
override func viewWillAppear(animated: Bool) {
self.clearsSelectionOnViewWillAppear = self.splitViewController!.collapsed
super.viewWillAppear(animated)
}
// MARK: - Search Controller
private func configureSearchController() {
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = false
searchController.searchBar.sizeToFit()
self.definesPresentationContext = true
self.tableView.tableHeaderView = searchController.searchBar
self.tableView.contentOffset = CGPoint(x: 0, y: searchController.searchBar.frame.height)
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
}
// MARK: - Segues
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let content = data[indexPath.row]
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController
controller.text = content
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
// MARK: - Table View
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let content = data[indexPath.row]
cell.textLabel!.text = content
return cell
}
}
Screenshots:
Related
I just started building an app and right now I am adding 2 Split View Controllers, in my Main.storyboard it looks like this image
I added the following code to my Master:
import UIKit
class ContactsMaster: UITableViewController {
var ContactsDetailController: ContactsDetail? = nil
var objects = [Any]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
navigationItem.leftBarButtonItem = editButtonItem
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:)))
navigationItem.rightBarButtonItem = addButton
if let split = splitViewController {
let controllers = split.viewControllers
ContactsDetailController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? ContactsDetail
}
}
override func viewWillAppear(_ animated: Bool) {
clearsSelectionOnViewWillAppear = splitViewController!.isCollapsed
super.viewWillAppear(animated)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#objc
func insertNewObject(_ sender: Any) {
objects.insert(NSDate(), at: 0)
let indexPath = IndexPath(row: 0, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
}
// MARK: - Segues
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showContactDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
let object = objects[indexPath.row] as! NSDate
let controller = (segue.destination as! UINavigationController).topViewController as! ContactsDetail
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
// MARK: - Table View
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let object = objects[indexPath.row] as! NSDate
cell.textLabel!.text = object.description
return cell
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
objects.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
}
And here is my Detail:
import UIKit
class ContactsDetail: UIViewController {
#IBOutlet weak var detailDescriptionLabel: UILabel!
func configureView() {
// Update the user interface for the detail item.
if let detail = detailItem {
if let label = detailDescriptionLabel {
label.text = detail.description
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
configureView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
var detailItem: NSDate? {
didSet {
// Update the view.
configureView()
}
}
}
My problem is when I run my app and goto the Split View Controller and select an item in the Master, it does not goto the Detail, but instead replaces the master.
I have a sample app that is just the Split View Controller and I noticed in the App Delegate file of the sample app there is this code in the application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool method:
let splitViewController = window!.rootViewController as! UISplitViewController
let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
splitViewController.delegate = self
And there is also this method:
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false }
if topAsDetailController.detailItem == nil {
// Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
return true
}
return false
}
My problem with this code is that my Split View Controller is not the inital controller and my problem with the splitViewController method, I have 2 split view controllers, I can only specificity 1 of them. How do I get this split view controller without making it the inital controller?
your master class must implement UISplitViewControllerDelegate.
so first thing you need to do :
class ContactsMaster: UITableViewController,UISplitViewControllerDelegate {
and override this function in your master:
func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController: UIViewController, ontoPrimaryViewController primaryViewController: UIViewController) -> Bool {
return true
}
then in viewDidLoad(master class) add following cods:
self.splitViewController!.delegate = self;
self.splitViewController!.preferredDisplayMode = UISplitViewControllerDisplayMode.AllVisible
self.extendedLayoutIncludesOpaqueBars = true
I think you skip many steps for configuring splitviewcontroller, if you want to understand all the way you can read one of many tutorials written for it, like:
http://nshipster.com/uisplitviewcontroller/
Did you by chance forget to set the ContactsDetail class for your Detail VC in your Storyboard?
I'm trying to segue from a search result in a tableview controller in swift 3 but when I use self.performSegue(withIdentifier: "teacherDetail", sender: self) it seems to hang when selecting a cell. If I tap the first cell it will show gray like it is selected, and then won't do anything unless I select another cell. Then, it will preform the segue to the detail view controller with the information from the first cell.
import UIKit
var name = ""
class DirectoryTC: UITableViewController, UISearchResultsUpdating {
var teachers = ["Mr. Delano", "Mr. Antani", "Mr. Botelho", "Mr. Braga"]
var filteredTeachers = [String]()
var searchController: UISearchController!
var resultsController = UITableViewController()
override func viewDidLoad() {
super.viewDidLoad()
self.resultsController.tableView.dataSource = self
self.resultsController.tableView.delegate = self
self.searchController = UISearchController(searchResultsController: self.resultsController)
self.tableView.tableHeaderView = self.searchController.searchBar
self.searchController.searchResultsUpdater = self
definesPresentationContext = true
}
func updateSearchResults(for searchController: UISearchController) {
self.filteredTeachers = self.teachers.filter{ (teacher:String) -> Bool in
if teacher.lowercased().contains(self.searchController.searchBar.text!.lowercased())
{
return true
}else
{
return false
}
}
self.resultsController.tableView.reloadData()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == self.tableView
{
return self.teachers.count
}else
{
return self.filteredTeachers.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = UITableViewCell()
if tableView == self.tableView
{
cell.textLabel?.text = self.teachers[indexPath.row]
}else
{
cell.textLabel?.text = self.filteredTeachers[indexPath.row]
}
return cell
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
DispatchQueue.main.async(){
if tableView == self.tableView
{
name = self.teachers[indexPath.row]
}else
{
name = self.filteredTeachers[indexPath.row]
}
self.performSegue(withIdentifier: "teacherDetail", sender: self)
}
}
}
And here is the swift file for the view controller I'm trying to segue to.
import UIKit
class DirectoryDetailVC: UIViewController{
#IBOutlet weak var test: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
test.text = name
}
}
make sure that your story board View Controller name is teacherDetail - use
DispatchQueue.main.async { self.performSegue(withIdentifier: "teacherDetail", sender: self)}
instead of
self.performSegue(withIdentifier: "teacherDetail", sender: self)
rmaddy caught it. It was a typo. "Typo - change didDeselectRowAtlectRowAt to didSelectRowAt"
In this split view was not displaying on the iPad screen if I drag it was displaying and if I select an index it is not displaying on the label
class ListTableViewController: UITableViewController {
let names = ["One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath)
cell.isSelected = true
cell.textLabel?.text = names[indexPath.row]
return cell
}
// MARK:- Storyboard segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "ShowDetailIdentifier") {
var detail: DetailsViewController
if let navigationController = segue.destination as? UINavigationController {
detail = navigationController.topViewController as! DetailsViewController
} else {
detail = segue.destination as! DetailsViewController
}
if let path = tableView.indexPathForSelectedRow {
detail.selectedIndex = path.row + 1
}
}
}
the code in master view controller
#IBOutlet weak var numberLabel: UILabel!
var selectedIndex:Int = 1
override func viewDidLoad() {
super.viewDidLoad()
numberLabel?.text = "\(selectedIndex)"
print(selectedIndex)
if splitViewController?.responds(to: #selector(getter: UISplitViewController.displayModeButtonItem)) == true {
navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
navigationItem.leftItemsSupplementBackButton = true
}
the code in details view controller
class SplitViewController: UISplitViewController,UISplitViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
splitViewController?.preferredDisplayMode = .primaryOverlay
splitViewController?.delegate = self
// Do any additional setup after loading the view.
}
func splitViewController(_ splitViewController: UISplitViewController,
collapseSecondary secondaryViewController: UIViewController,
onto primaryViewController: UIViewController) -> Bool {
return true
}
the code for split view controller
you are missing thisatableView.reloadData()on yourviewDidLoadorviewDidAppear
I can't see where are you initializing the splitviewcontroller you need to pass the TableViewController, and detailViewController.
You need to pass it in the viewDidLoad of the class inheriting form UISplitViewController
self.viewControllers = [masterNav, detail]
and to always show splitviewcontroller you need this
self.displayMode = .allVisible
I'm building an app in which one of the functions will be sorting all of the shops in the main shopping street of our town. This is why I made a table view with some shops listed inside, with a category and a name.
I sorted them alphabetically, and made a search bar for it as you can see in this video. Something goes wrong with the navigation though, it works when not using the search bar, but it disappears when using it.
Can anybody help me with this? Thanks in advance! The code for the main view controller (which contains the complete navigation, since nothing about it's done in the storyboard) is mentioned below:
import UIKit
class MasterTableViewController: UITableViewController {
//winkels filteren
var filteredWinkels = [Winkel]()
// MARK: - Properties
var detailViewController: DetailViewController? = nil
var winkels = [Winkel]()
// MARK: - View Setup
override func viewDidLoad() {
super.viewDidLoad()
winkels = [
Winkel(category:"Literature", name:"Standaard"),
Winkel(category:"Literature", name:"Acco"),
Winkel(category:"Clothing", name:"H&M"),
Winkel(category:"Clothing", name:"C&A"),
Winkel(category:"Clothing", name:"Patio"),
Winkel(category:"Restaurants", name:"De 46"),
Winkel(category:"Cafés", name:"'t Hoekske"),
Winkel(category:"Supermarkets", name:"Carrefour"),
Winkel(category:"Supermarkets", name:"Colruyt"),
Winkel(category:"Supermarkets", name:"Blokker"),
Winkel(category:"Lingerie", name:"Hunkemoller")
]
//alfabetisch sorteren
winkels.sortInPlace({ $0.name < $1.name })
if let splitViewController = splitViewController {
let controllers = splitViewController.viewControllers
detailViewController = (controllers[controllers.count - 1] as! UINavigationController).topViewController as? DetailViewController
}
//searchController aanmaken
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
//searchButtons aanmaken
searchController.searchBar.scopeButtonTitles = ["All", "Clothing", "Supermarkets", "Literature"]
searchController.searchBar.delegate = self
self.tableView.reloadData()
}
override func viewWillAppear(animated: Bool) {
clearsSelectionOnViewWillAppear = splitViewController!.collapsed
super.viewWillAppear(animated)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table View
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
//aantallen tellen
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchController.active && searchController.searchBar.text != "" {
return filteredWinkels.count
}
return winkels.count
}
//naam cel aanpassen en checken
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let winkel: Winkel
if searchController.active && searchController.searchBar.text != "" {
winkel = filteredWinkels[indexPath.row]
} else {
winkel = winkels[indexPath.row]
}
if winkel.name .containsString("Hunkemoller") {
cell.textLabel!.text = "Hunkemöller"
} else {
cell.textLabel!.text = winkel.name
}
cell.detailTextLabel?.text = winkel.category
return cell
}
// Segues voorbereiden
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
let winkel: Winkel
if searchController.active && searchController.searchBar.text != "" {
winkel = filteredWinkels[indexPath.row]
} else {
winkel = winkels[indexPath.row]
}
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController
controller.detailWinkel = winkel
controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("showDetail", sender: self)
}
//searchcontroller aanmaken
let searchController = UISearchController(searchResultsController: nil)
//scopebar maken
func filterContentForSearchText(searchText: String, scope: String = "All") {
filteredWinkels = winkels.filter { winkel in
let categoryMatch = (scope == "All") || (winkel.category == scope)
return categoryMatch && winkel.name.lowercaseString.containsString(searchText.lowercaseString)
}
tableView.reloadData()
}
}
// updaten
extension MasterTableViewController: UISearchResultsUpdating {
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchBar = searchController.searchBar
let scope = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex]
filterContentForSearchText(searchController.searchBar.text!, scope: scope)
}
}
extension MasterTableViewController: UISearchBarDelegate {
func searchBar(searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
filterContentForSearchText(searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
}
}
UISearchController has a property named hidesNavigationBarDuringPresentation, default is YES. I don't make sure if it is the root cause.
You can implement - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath of UITableViewDelegate, use self.navigationController.pushViewController to push your DetailViewController instead of using segue
You can set your navigation bar status of destination view controller in your prepareForSegue: method:
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController
controller.detailWinkel = winkel
controller.navigationBarHidden = true
I have a strange problem in iOS 9 with Swift 2.0. I added UISearchController in my tableViewController but it causes a strange black screen problem. When I press the search bar and write something it shows my filtered results without any problem but when I tap another tab bar button like Bookmarks and after that when I tap tableViewController which is Most Viewed again it shows black screen like screen shot.
There is my tableViewController;
import UIKit
class CitiesTableViewController: UITableViewController, UISearchResultsUpdating {
// MARK: - Class Properties
private var cities = [String]()
private var veterinaries = [Veterinary]()
private var filteredVeterinaries = [Veterinary]()
private var resultSearchController: UISearchController!
// MARK: - TableViewController Life Cycle Methods
override func viewDidLoad() {
super.viewDidLoad()
self.getCitiesList()
self.configureResultsSearchController()
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
self.resultSearchController.active = false
}
// MARK: - Configuring Search Bar Controller
private func configureResultsSearchController() {
self.resultSearchController = UISearchController(searchResultsController: nil)
self.resultSearchController.searchResultsUpdater = self
self.resultSearchController.dimsBackgroundDuringPresentation = false
self.resultSearchController.hidesNavigationBarDuringPresentation = false
self.resultSearchController.searchBar.sizeToFit()
self.resultSearchController.searchBar.placeholder = "Klinik veya ilçe adı"
self.tableView.tableHeaderView = self.resultSearchController.searchBar
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 }
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.resultSearchController.active { return self.filteredVeterinaries.count }
else { return self.cities.count }
}
// MARK: - Table view Delegate Methods
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if (self.resultSearchController.active) {
self.performSegueWithIdentifier(Constants.ShowDetailViewControllerSegueIdentifier, sender: nil)
} else {
self.performSegueWithIdentifier(Constants.ShowTownsTableViewControllerSegueIdentifier, sender: nil)
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(Constants.CellIdentifier, forIndexPath: indexPath)
if (self.resultSearchController.active) {
cell.textLabel?.text = self.filteredVeterinaries[indexPath.row].name
cell.detailTextLabel?.text = self.filteredVeterinaries[indexPath.row].address
return cell
} else {
cell.textLabel?.text = self.cities[indexPath.row]
return cell
}
}
// MARK: - PARSE Query Methods
private func getCitiesList() {
let parseQueries = ParseQueries()
parseQueries.downloadListData() {
(let parseResults) in
if let veterinaries = parseResults as? [Veterinary] {
self.veterinaries = veterinaries
for vet in veterinaries {
if let city = vet.city {
self.cities.append(city)
}
}
dispatch_async(dispatch_get_main_queue()) {
self.cities = HelperMethods().removeDuplicatesAndSort(array: self.cities)
self.tableView.reloadData()
}
}
}
}
// MARK: - UISearchController Delegate Methods
func updateSearchResultsForSearchController(searchController: UISearchController) {
self.filteredVeterinaries.removeAll(keepCapacity: false)
if let searchBarText = searchController.searchBar.text{
let searchText = searchBarText.lowercaseString
// Searching with Veterinary Name and Veterinary City
self.filteredVeterinaries = self.veterinaries.filter({$0.name?.lowercaseString.rangeOfString(searchText) != nil})
self.filteredVeterinaries += self.veterinaries.filter({$0.town?.lowercaseString.rangeOfString(searchText) != nil})
tableView.reloadData()
}
}
This is the black screen image from iOS 9 simulator same as real device.
I think its deiniting my tableView when I tap the searchBar and it can't init again. Is this a bug or something ?
How can I solve this problem ?
Thank you !
Friend, in your viewDidLoad() insert this code line:
self.definesPresentationContext = true
See how I put (line 29):
click here to see
I recently faced the same issue and I could fix it easily.
self.definesPresentationContext = true was already defined in viewDidLoad() but it didn't solve this issue.
In the above question, maybe you show CitiesTableViewController directly when you tap MostViewed tabbar item.
In order to avoid the black screen, you can embed CitiesTableViewController in a UINavigationController and try to show the navigationcontroller when tapping the tabbar item.
This solution will avoid the black screen issue.