xCode 10.2.1, iOS 12.1
I'm using a tableView with a searchbar. Everything is working fine, except a strange behavior when the navigationBar is visible during a search.
searchController.hidesNavigationBarDuringPresentation = false
When I leave the default setting (hidesNavigationBarDuringPresentation = true) the back button and title disappears, if I enter something into the searchbar. Then I have to press the search bar's cancel button and afterwards the back button. The Method viewWillDisappear() gets called and isMovingFromParent is set to true.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if isMovingFromParent {
...
}
}
Everything works fine, I just don't like this behavior, to have to tap on cancel and on the back button.
I would like to enter a search string, see my results in my tableview, maybe select some of them and without tapping on the search bar's cancel button, just tap on the back button.
Therefore I set searchController.hidesNavigationBarDuringPresentation = false and tried it.
But when doing this isMovingFromParent is suddenly false.
Can someone please explain me why this happens, at least for me it makes no sense.
Related
I have a pretty standard iOS page layout like the following. There's a search bar and there's a back button on the navbar to go back to the parent VC.
I have code to set the tint color of the navbar into some custom color, like the following:
self.navigationController?.navigationBar.tintColor = COLOR
which works fine. However, if I click on the search bar (which brings it to the top and focuses on it) and then dismiss it, the back button becomes apple default blue. If I go to another app then come back, or use the interactive pop recognizer to go back to the parent VC but cancel it midway (so I return to this VC), the black color is restored. However, even if I call the above method in didDismissSearchController(_ searchController: UISearchController), I can't set the color to something other than blue.
I think it's likely an Apple bug. That said, I'd love to know if there may be a hack to make it work. Fwiw, when inspecting the view hierarchy, the navbar actually has the correct tint color, but the back button doesn't. Setting tint color for the back button however has no effect whatsoever.
I had faced the similar issue in iOS 13.1
and the way I fixed it was a bit odd.
I added custom back button on navigation bar.
Here is my code
You can create a new back button in UISearchBarDelegate function searchBarCancelButtonClicked(_ searchBar: UISearchBar)
func searchBarCancelButtonClicked(_ searchBar: UISearchBar)
{
//Hide Cancel
searchBar.setShowsCancelButton(false, animated: true)
searchBar.text = String()
searchBar.resignFirstResponder()
if #available(iOS 13.0, *) {
createBackButton()
}
}
func createBackButton() {
let image = UIImage.init(named: "BackIcon")
let customBackButton = UIBarButtonItem(image: image, style: .done, target: self, action: #selector(backButtonPressed))
navigationItem.leftBarButtonItem = customBackButton
navigationItem.leftBarButtonItem?.setBackButtonTitlePositionAdjustment(.zero, for: .default)
}
#objc func backButtonPressed() {
navigationController?.popViewController(animated: true)
}
I'm using a search controller in order to implement a search section in an iOS app. I'm using iOS 11 and xCode 10.
My scene is a Table View Controller. I have included a search bar programmatically with the following code:
override func viewDidLoad() {
super.viewDidLoad()
let searchController = UISearchController(searchResultsController: nil)
searchController.searchBar.delegate = self
definesPresentationContext = true
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
}
I have also included a UISearchBar delegate extension:
extension SearchTableViewController: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
tableView.reloadData()
episodeResultsArray = []
let searchTerm = searchBar.text!
let escapedSearchTerm = searchTerm.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!
performSearch(forTerm: escapedSearchTerm)
}
}
The performSearch function connects to a database via AlamoFire and returns the results which are then listed in the Table View.
When I run the app, the table appears empty (as it should be initially). When I click on the search bar, it enters the editing state (the navigation bar collapses, the search bar sticks to the top, a Cancel button appears, and a translucent grey box covers the rest of the screen.
When I search for something, the results are succesfully listed in the table. However, even though the results are visible, I still remain in the editing state for the search bar. I need to click on the translucent box to exit the editing state and only then I am able to scroll through the results.
How can I exit the editing state after I click on Search so I'm able to scroll through the table as soon as I get the results?
I tried using searchBar.resignFirstResponder() but this only dismisses the keyboard. I also tried
searchBar.endEditing(true)
but even though the search bar loses focus, I'm still in the editing state.
Just to clarify, I need for this to work as a search from scratch, not as a filter, meaning not just showing a subset of results as I type.
Found the answer. The solution is to add
searchController.isActive = false
Overview:
I would like to set the accessibility focus to the navigation bar's title item.
By default the focus is set from top left, meaning the back button would be on focus.
I would like the title item to be in focus.
Attempts made so far:
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification,
navigationController?.navigationBar.items?.last)
Problem:
The above code makes no difference, the back button is still in focus.
Possible Cause:
Not able to get the item corresponding to the title to be able to set the focus.
Solution 1
I don't like it, but it was the minimum amount of hacking that does not rely on digging through hidden subviews (internal implementation of UINavigationBar view hierarchy).
First in viewWillAppear, I store a backup reference of the back button item,
and then remove the back button item (leftBarButtonItem):
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
backButtonBackup = self.navigationItem.leftBarButtonItem
self.navigationItem.leftBarButtonItem = nil
}
Then I restore the back item, but only after I dispatch the screen changed event in viewDidAppear() :
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
self?.navigationItem.leftBarButtonItem = self?.backButtonBackup
}
}
Solution 2:
Disable all accessibility on the nav bar and view controller up until viewDidAppear() is finished:
self.navigationController.navigationBar.accessibilityElementsHidden = true
self.view.accessibilityElementsHidden = true
, and then in viewDidAppear manually dispatching the layout element accessibility focused event to the label subview of UINavigationBar:
UIAccessibilityPostNotification( UIAccessibilityLayoutChangedNotification, self.navigationController.navigationBar.subviews[2].subviews[1])
// The label buried inside the nav bar. Not tested on all iOS versions.
// Alternately you can go digging for the label by checking class types.
// Then use DispatchAsync, to re-enable accessibility on the view and nav bar again...
I'm not a fan of this method either.
DispatchAsync delay in viewDidAppear seems to be needed in any case - and I think both solutions are still horrible.
I invoked UIAccessibilityScreenChangedNotification on navigation title from viewDidLoad and it worked
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification,
self.navigationItem.title);
First, we'll need to create an useful extension:
extension UIViewController {
func setAccessibilityFocus(in view: UIView) {
UIAccessibility.post(notification: .screenChanged, argument: view)
}
}
Then, we'll be able to set our focus in the navigation bar title like this:
setAccessibilityFocus(in: self.navigationController!.navigationBar.subviews[2].subviews[1])
iOS 11.2, Xcode 9.2
I've tried all the ways to dismiss keyboard on viewDidDisappear of the old UIViewController AFTER a new UIViewController is pushed into UINavigationController stack. But with no luck.
If I dismiss it on viewWillDisappear - it will be dismissed but with animation DURING push animation. It is not the desired behaviour. I want the old UIViewController's keyboard to be dismissed only when the controller is no longer visible.
The behavior should be like in Telegram app:
In any dialog with visible keyboard press on opponents avatar and you'll be pushed to opponents account information. Then, if you press back button, you'll be redirected back to a dialog. But the keyboard will be already dismissed.
Any help is appreciated!
P.S. The question might look like a duplicate, but I have failed to make it work with the solutions I found.
Edit 1.
I have created a small TEST PROJECT which represents a failure to achieve the desired behavior.
In order to reproduce the undesired behavior:
Launch the app.
Tap on UITextField or UITextView and wait for the keyboard to appear.
Tap on "Next" button and wait for a new controller to be pushed.
Tap on "Back" button and wait for a new controller to be popped.
As a result - the initial view controller will have the active keyboard after the push/pop actions. I need the keyboard to be hidden after the push/pop actions. Also, the keyboard should not be dismissed before the initial view controller becomes invisible, it should be dismissed after viewDidDisappear action.
There are cases where no text field is the first responder but the keyboard is on screen. In these cases, the above methods fail to dismiss the keyboard.
Use the property: isEditable of your textView. Here is a tested code:
override func viewWillAppear(_ animated: Bool) {
self.viewTextView.isEditable = false
super.viewWillAppear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.viewTextView.isEditable = true
}
Results:
Comments from #iWheelBuy:
Sometimes, text views will have inputAccessoryView. The way you do it
will make the keyboard disappear, but the inputAccessoryView will
remain... That is why you should also make inputAccessoryView = nil
or inputAccessoryView = UIView() when setting isEditable = false
The problem happens because responders are managed (restored, saved) by UIKit between viewWillAppear and viewDidAppear, just before view has appeared, and between viewWillDisapear: and viewDidDisapear:, just before view has disappeared. That is why any change made to responders is visible during animation.
Instead of removing responders, to get the effect you want, you can prevent views from becoming responders before view appears again.
The simplest way to do this for UITextField and UITextView is to temporary disable interaction just before view will appear, and then restore it after the view did reappeared.
override func viewWillAppear(_ animated: Bool) {
self.viewTextField.isUserInteractionEnabled = false
self.viewTextView.isUserInteractionEnabled = false
super.viewWillAppear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.viewTextField.isUserInteractionEnabled = true
self.viewTextView.isUserInteractionEnabled = true
}
This will give you the same effect Telegram has.
I am using a Show segue in my application.
Whenever I segue to another screen and press the back bar button, my navigationController.toolbar disappears.
I tried to get rid of it with
navigationController?.toolbar.hidden = false
in my viewDidLoad().
It doesn't work though. Any ideas?
Please add the code in the viewWillAppear() and it should solve the problem you are facing.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
navigationController?.toolbarHidden = false
}
Remember that viewDidLoad() fires only once during the life cycle of a view controller and in your case , it is in the navigation stack which means it has been already used for that view controller and now when you press back button, it does not work again.
navigationController?.toolbarHidden = false