I have an UITableView which works perfectly fine. It is nested in a UITabbarController.
I also integrated an UISearchController with a UISearchBar, which lets the user search the content of the UITableView. This also works fine.
When you select one of the TableViewCells, you get to a DetailView.
When you do this without active search, everything works fine, but when you enter a searchterm and you choose a cell from the results, there is no way to get back to the TableView as there is no back button in the top left corner, neither can you use the swipe back gesture.
I may have to add, that I do not specify a separate UITableView for the searchresults:
self.searchController.searchBar.delegate = self
self.searchController.searchResultsUpdater = self
self.tableView.tableHeaderView = self.searchController.searchBar
self.definesPresentationContext = true
I define the SearchController like this:
var searchController: UISearchController = {
let controller = UISearchController(searchResultsController: nil)
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
return controller
}()
I thought, that maybe I have to put a UINavigationController into the UITabbarController and set my TableView as its RootViewController, but then I had the problem, that the SearchBar was hidden behind the NavigationBar...
EDIT (#ryantxr):
my didSelectRowAtIndexPath:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
in cellForRowAtIndexPath there is no difference between search and no search as the cells look the same. This is also the reason, why I don't use two different segue yet.
But you are right, I don't use different segue when using search and no search. The only segue used when a cell is clicked is setup in the storyboard and in prepareForSegue I set the attributes of the target UIViewController, which also works fine.
Turns out that the problem was this little piece of code in the UITableViewController which contains the UISearchController was responsible for this:
self.definesPresentationContext = true
by deleting this, the result view controller was shown correctly and the navigationbar did not disappear so it was possible to go back to the searchresults ect...
Related
I have a view controller with a tableview containing a list of chats, a search controller enbeded in the navigation item (iOS 11 feature)
let searchController = UISearchController(searchResultsController: nil)
searchController.dimsBackgroundDuringPresentation = false
navigationItem.searchController = searchController
definesPresentationContext = true
When the user taps a chat in the table view the app pushes a new view controller with another table view containing messages for that chat. That works like it is supposed to:
The problem is that when the user activates the search controller, find some chat and taps it, the pushed view controller containing table view with the chat messages does some really strange animation with the table view that should not happen:
I load the data before the actual navigation and bind it to the table view in viewDidLoad using just reload() on the table view. The problematic table view uses auto layout and custom cells.
The problem is very similar to UITableView has unwanted animation when reloadData is called but for me it only happens when the iOS 11 search controller is active.
Edit: If I remove tableView.rowHeight = UITableViewAutomaticDimension and use a fixed height using func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat the problem is still there
If you just hide the searchBar before pushing a new viewController then it may fix your problem.
You need to create a global variable for searchBarCancelButton and find the cancel button from its subviews when you search something
let buttons = searchController.searchBar.subviews.first?.subviews.filter { (view) -> Bool in
return NSStringFromClass(view.classForCoder) == "UINavigationButton"
} as? [UIButton]
searchBarCancelButton = buttons?.first
then you can manually cancel it.
self.searchBarCancelButton?.sendActions(for: .touchUpInside)
Personally, I would simply hide the searchView controller before presenting the new view controller.
( Using UIView.animates with a completion handler for example )
I would not try to investigate further because since iOS11, there is an esoteric problem in the safe area management. A bug ? :)
Even the launch screens layouts are not correctly handled.
So many majors logos miss their middle part at launch !
You can try to call cell.layoutIfNeeded() right after dequeuing and setting a content of the cell
iOS 11 completely revamped the safe area API, including scroll view inset adjustment behaviors, which can cause unwanted animation when ignored. Therefore, disable automatic content inset adjustment for the scroll view with the unwanted animation:
if #available(iOS 11.0, *) {
tableView.contentInsetAdjustmentBehavior = .never
} else {
// < iOS 11 logic
}
Do not invoke reloadData() method in your viewDidLoad or viewWillAppear. Instead of this reload your tableView with empty data in viewDidLoad so your tableView will show nothing and then in your viewDidAppear invoke reloadData() method to load all of your chats. This will restrict your tableView from loading with unwanted animation.
var shouldShowEmpty = true
func viewDidLoad() {
super.viewDidLoad()
tableView.reloadData()
}
func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
shouldShowEmpty = false
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if shouldShowEmpty {
return 0
}
return yourArray.count
}
As per attachment, It looks like due to auto layout, cell's element's height was zero (UILabel or UIImageView) and then suddenly when table view reloads it gets the data inside the cell which increase its height(automatic dimension or whatever) which cause this animation.
Try to push it without animation or try to set fix cell's element's height and width and check is it still showing this animation.
Did you check without invoking UISearchBar, If you select on any cell same animation is happening ?? or did you try to remove UISearchbar and select on the cell and check the animation part ?
Please share your code so we can see more clearly.
I have had a similar problem, and I believe that solution is the same. The keyboard is causing the problem, to be more correct, keyboardWillHideNotification trigger. You have text field at the bottom, that probably listens to notifications for keyboard show/hide, where layoutIfNeeded() is triggered if you animate bottom constraint so that your keyboard doesn't overlap your text field. So when you finish your search in the search text field, keyboardWillHideNotification gets triggered at an unwanted time. I solved my issue by calling:
resignFirstResponder()
for the text field that causes this event. In my case, that was after pressing the button, in your, I believe it's in didSelect tableView cell in the search table view.
If you are still active at this issue, please let me know if you manage to solve it. I broke my head trying to solve this issue, which apparently is so simple and straightforward.
In VC2 Try a delay function before reloading table
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
tableView.reloadData
}
or else
In VC1 in didSelect resign first responder of search controller before pushing to VC2
or
In VC1 in didSelect resign first responder of search controller and set a delay before pushing to VC2.
Problem:
I have a table view that the user can either scroll through to find something or use a search bar. The search bar was not created using the StoryBoard. My view has a UISearchController that handles the search bar and search result updating. The issue that I'm running into is that since my SearchResultsController is instantiated by another controller I cannot perform a segue to another view, or at least I can't find the solution. Everything works except for the segue between my Search Results Controller and the view it's destined for.
What I would like to do
Have my MyNavTableViewController delegate for MySearchResultsController. In the search results controller it will segue to another view when the user taps on a TableViewCell. I'm not using the StoryBoard to accomplish this I'm having difficulty using segues to transition to another view.
If I can't get this to work what I will probably do:
It's essential that I pass information between views, and for me I've always done it using segues. However if this doesn't work I will probably try presenting a view modally by pushing it unto the navigation controller stack and write the data to a shared database or something. I would prefer to use a segue though.
Research:
There is definitely more than this, but I'm not going to take too much space on urls.
Creating a segue programmatically
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/UsingSegues.html
My Setup
I'm going to try and keep this as concise as possible. There is more code than what I'm displaying. I'm just going to try and clean it up so that I'm only presenting the important stuff. I'm also changing a few names around because there may be sensitive information. It's probably not a big deal, but I'd rather be safe.
class MyNavTableViewController: UIViewController, UITableViewDataSource{
//this is
#IBOutlet weak var tableView: UITableView!
var searchController: UISearchController!
override func viewDidLoad(){
...code
tableView.registerClass(UITableViewCell.self,forCellReuseIdentifier: tblId)
let resultsController = MySearchResultsController()
resultsController.databaseFilePath = databaseFilePath()
//this is essential that I use a segue because between my views I'm passing information between them.
resultsController.photo = photo!
searchController = UISearchController(searchResultsController: resultsController)
let searchBar = searchController.searchBar
searchBar.placeholder = searchBarPlaceHolderText
searchBar.sizeToFit()
tableView.tableHeaderView = searchBar
searchController.searchResultsUpdater = resultsController
}
}
MySearchResultsController: UITableViewController, UISearchResultsUpdating {
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
//self.performSegueWithIdentifier(imagePrepareStoryBoardId, sender: tableView.cellForRowAtIndexPath(indexPath))
/*let imagePrepare = ImagePrepareController()
customSegue = SearchResultSegue(identifier: imagePrepareId, source: self, destination: imagePrepare)*/
//neither storyboard nor navigationController can be nil.
let destVC = self.storyboard!.instantiateViewControllerWithIdentifier(imagePrepareStoryBoardId)
//destVC.photo = photo!
//self.presentViewController(destVC, animated: false, completion: nil)
self.navigationController!.pushViewController(destVC, animated: false)
}
}
My Failed Attempts
1) Straight up segue - Doesn't work since the MySearchResultsController is not a view in the storyboard. Everything from what I've read is that segues can only be created in the SB.
2) Push view onto the navigation stack. The only problem with this is that I can't send data between views (or at least from what I've read). I'm also getting this error right at this break point:
let destVC = self.storyboard!.instantiateViewControllerWithIdentifier(imagePrepareStoryBoardId)
fatal error: unexpectedly found nil while unwrapping an Optional value
I double checked the imagePrepareStoryBoardId. It's correct.
3) Use custom segue - As you can see from the commented out lines that this isn't working either. I've always used segues with the SB so this method is a little new to me. I might be messing it up somewhere.
First create a protocol
protocol SelectedCellProtocol {
func didSelectedCell(text: String)
}
on your UITableViewClass declare it
class MySearchResultsController: UITableViewController, UISearchResultsUpdating {
var delegate:SelectedCellProtocol?
}
and on the selected cell method call it like :
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
self.delegate?.didSelectedCell(cell.textLabel?.text)
}
when you declare your results controller, set the delegate
let resultsController = MySearchResultsController()
resultsController.databaseFilePath = databaseFilePath()
//this is essential that I use a segue because between my views I'm passing information between them.
resultsController.photo = photo!
resultsController.delegate = self
searchController = UISearchController(searchResultsController: resultsController)
let searchBar = searchController.searchBar
searchBar.placeholder = searchBarPlaceHolderText
searchBar.sizeToFit()
tableView.tableHeaderView = searchBar
searchController.searchResultsUpdater = resultsController
and then implement the protocol
class MyNavTableViewController: UIViewController, UITableViewDataSource, SelectedCellProtocol{
func didSelectedCell(text: String) {
print(text)
//make your custom segue
}
}
I have made a UITableView Controller with a UISearchBar as the Table's header.
I have then embedded this View Controller into a UINavigationController, as the root view controller.
Now, when I tap on the Search Bar, the SearchBar seems to disappears and displays a white screen. The keyboard appears, but there is no Search Bar.
The Table View can scroll, but the search bar has simply vanished.
When I implement this UITableViewController without the Navigation Controller, it works perfectly. But something about the Navigation Controller is borking everything up.
I've had the same issue that was sometimes happening, especially with table view of small number of rows (less than 50).
It appears the searchBar is removed from the view hierarchy, precisely from the container view that is a child of the UISearchControllerView.
I've found a workaround to manually add back the searchbar as a subview of the UISearchControllerView container child. This is implemented in the delegate function (from UISearchControllerDelegate) didPresentSearchController:
func didPresentSearchController(searchController: UISearchController) {
if searchController.searchBar.superview == nil {
for searchCtrlChildView in searchController.view.subviews {
if searchCtrlChildView.frame.origin == CGPoint(x: 0, y: 0) { //Discriminate if by chance there was more than one subview
searchCtrlChildView.addSubview(searchController.searchBar)
break
}
}
}
}
I've also filed a radar to Apple on this as it is not fixed in iOS 8.4
Check the way I had my searchBar it in viewDidLoad
I have my viewController embedded in NavigationController too
My code (hope it helps) :
class myTableViewController: UITableViewController,UISearchResultsUpdating,UISearchControllerDelegate,UISearchBarDelegate
override func viewDidLoad() {
super.viewDidLoad()
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
So, I am currently trying to replace the depricated searchDisplayController in one of my projects with UISearchController and I am running into this problem.
If there are no results in the search (the UITableView is empty) the whole ViewController is dismissed. This does not happen when the search results are not empty. I wan't to make it clear I am not using a UITableViewController. Instead I have a regular VC with a UITableView in it.
Here is some of my code:
var resultSearchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
controller.delegate = self
controller.searchBar.delegate = self
self.studentTable.tableHeaderView = controller.searchBar
return controller
})()
....
}
Now, if I add this function to the equation the cancel button always dismisses the VC.
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
resultSearchController.active = false
}
So why exactly does setting the searchController.active = false dismiss the VC? Is it because it is using the same UITableView as the VC? I believe that the old searchDisplayController would just display a UITableView over the one being used. If this is the case is there a way to override the dismissVC?
this is also Happening to me. The Way I Solve it is by Replacing:
resultSearchController.active = false
with
resultSearchController.searchBar.text = ""
resultSearchController.searchBar.resignFirstResponder()
I Hope this helps you :-)
2018 Just wanna share the fruits of my 1-2 hours debugging.
I had multiple issues with using UISearchController with UITabBarController, namely:
This one, this very question of the OP. Hitting cancel button dismisses the screen that is presenting the searchController.
The tab (or the screen) becomes black, Tab Bar and UISearchController giving black screen
Using UISearchController inside the title view of the navigation bar of UINavigationController in both iOS 10, 11, and 12, like this questions. UISearchBar increases navigation bar height in iOS 11
And for the solution for #3, since we're already here: https://stackoverflow.com/a/53264329/3231194
Finally, the ONLY solution that I have been seeing all this time is adding this code:
self.definesPresentationContext = true
The issue is that I was putting this in a wrong function.
Remember, that solution solved the #1, and #2 problem that I had. Nothing more, nothing less.
Where to add that? Inside the viewDidAppear. That's it!
I have a problem with a UISearchBar. When ill search some Text in combination with an UITableView, and ill click on one result Cell, the UISearchBar is still visible in the next View Controller. If ill go back (with Segues) - the UISearchbar is still there (with the Keyword)
So after ill click on one result, ill get (in the next View Controller):
Ill use it this way:
class ...: UITableViewController, UISearchResultsUpdating {
var filterSearchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
filterSearchController.searchResultsUpdater = self
filterSearchController.hidesNavigationBarDuringPresentation = false
filterSearchController.dimsBackgroundDuringPresentation = false
filterSearchController.searchBar.searchBarStyle = .Minimal
filterSearchController.searchBar.sizeToFit()
self.tableView.tableHeaderView = filterSearchController.searchBar
Any ideas what could be a problem?
You need to dismiss the UISearchController yourself before transitioning to the next view controller with:
filterSearchController.active = false