Unwanted Keyboard after Back Navigation - ios

All UIViewControllers in my app are managed by a top level UINavigationController. In the UIViewController that is currently on top of my navigation stack I have a set of UITextFields.
A problem occurs when I call becomeFirstResponder() on one of these text fields and then immediately navigate back without changing focus first, e.g. by tapping on another field. After navigating back one level, the keyboard appears, and I have found no way to keep it from appearing or making it disappear. It even stays as I further push views of the navigation stack.
This is directly or indirectly connected to the
becomeFirstResponder() call, because without that call, the
problem does not occur.
Even if I, for test purposes, call
resignFirstResponder() immediately after becomeFirstResponder()
the keyboard still appears after navigating back.
I have tried other ways like detecting and resigning the first responder or calling endEditing() in viewWillDisappear() but did not succeed. I’m not even sure what this keyboard belongs to after the corresponding view is popped off the stack. I cannot inspect the keyboard in the View Debugger as it does not appear there.
Why does the keyboard appear, and how can I prevent it?

It turns out that the form validation that was grabbing the first responder kept reclaiming it until the field content was valid. If it does not release the status before back navigation the keyboard stays and it becomes difficult to assign first responder to another control.
Solution in my case was to more carefully keep track of which field is first responder, detect the back button push, allow resigning first responder unconditionally in that case, and then resign first responder for that field.
var currentTextField: UITextField?
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if let currentField = self.currentTextField {
currentField.resignFirstResponder()
}
}
override public func willMoveToParentViewController(parent: UIViewController?) {
if (parent == nil) {
backButtonPushed = true
}
super.willMoveToParentViewController(parent)
}
func customTextFieldDidBeginEditing(textField: UITextField) {
currentTextField = textField
}
public func textFieldShouldEndEditing(textField: UITextField) -> Bool {
// ...
// Must return true if back button is pushed.
if backButtonPushed {
return true
} else {
// ...
}
}

Did you try to call endEditing() in viewWillAppear() of the new VC instead?

Related

Swift multiple UITexView resign responder handling

In my case, I am having two textview one I placed into view another one within UIAlertController. Here, I added done and cancel button on keyboard accessory with actions. Now, how to create resign responder for both UITextView?
#IBAction func doneClick(_ sender: Any) {
self.descriptionTextView.resignFirstResponder()
self.textView.resignFirstResponder() // Its making crash sometime
}
It appears that one of them is nil at some time , so You can do
self.view.endEditing(true)
Or make them optional like
self.descriptionTextView?.resignFirstResponder()
self.textView?.resignFirstResponder()

Accessibility set focus to navigation bar title item

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])

Keyboard does not disappear after viewDidDisappear

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.

IOS swift - How can I end the editing in textfield after opening a new view controller when textfield is tapped

hello I have a text field on my view controller. I am launching another view controller when user taps or click in the textfield. It is working successfully but the problem is when I press the back button from second view controller, It shows first controller and immediately it again redirect me to the 2nd view controller. screen doesn't stays on 1st controller. it is because cursor is there on the textfield so thats why.I don't no how can I make text field inactive after I move to the 2nd controller. hope you understand my question.
here is the code I am using
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
self.navigationController!.pushViewController(self.storyboard!.instantiateViewControllerWithIdentifier("LoginViewController") as UIViewController, animated: true)
//delegate method
return true
}
return false in the given method, it means your keyboard will not show and you will directly redirect to your 2nd viewcontroller.
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
self.navigationController!.pushViewController(self.storyboard!.instantiateViewControllerWithIdentifier("LoginViewController") as UIViewController, animated: true)
//delegate method
return false // Change here return false it means it won't show keyboard
}

How to hide Bar Button Items when UITableView is in edit mode? (Swift)

Title essentially says it all.
I have a UITableView and I want the RightBarButtonItem item to disappear while the UITableView is in edit mode. Unfortunately, all of the answers that I have found so far suggest setting the button to nil... which won't work for me because I do not want to get rid of the button and the reference to it, just hide it while the UITableView is in edit mode.
What I'm having trouble figuring out what to do, then, is:
Detect when the UITableView has entered editing mode
Hiding the RightBarButtonItem (not removing it entirely)
Detect when the UITableView has left editing mode (so the button can reappear)
Any help would be appreciated, thank you!
My working solution for anyone who needs it:
override func setEditing(editing: Bool, animated: Bool) {
if (editing) {
super.setEditing(true, animated: true)
self.navigationItem.rightBarButtonItem!.enabled = false
} else {
super.setEditing(false, animated: true)
self.navigationItem.rightBarButtonItem!.enabled = true
}
}
Make sure to set the super.setEditing before and after editing starts in order to preserve the functionality of the edit button.
In addition, if you don't want the UITableView to remain in edit mode when the user leaves the UITableView and doesn't click "done" first, add the following function:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(true)
if (editing) {
editing = false
}
}
You can use an optional datasource method to detect when a row is being edited, tableView(_:canEditRowAtIndexPath:)
And inside that method, you can either hide or disable the bar button item (disabling is probably the friendlier thing to do, in terms of the UI and code). There is no hidden property on a bar button, so to properly hide it means you potentially do some grody coding to temporarily remove or make it disappear.
Anyways, I suggest something like:
func tableView(tableView: UITableView!, canEditRowAtIndexPath indexPath: NSIndexPath!) -> Bool {
self.navigationItem.rightBarButtonItem.enabled = false
return true
}
I had same challenge with two different bar buttons which I needed to hide and show on some conditions. First was "edit" button to enter editing mode on my view and second was "done" to save changes. So they had to appear interchangeably.
I made an extenstion to UINavigationItem class where implemented two methods. One to delete item from bar. Second to add item to bar. As a parameter to these methods I passed IBOutlets of the items which I made strong so they would not deallocate when item is removed from bar
extension UINavigationItem {
func deleteFromRightBar(item: UIBarButtonItem) {
// make sure item is present
guard let itemIndex = self.rightBarButtonItems?.index(of: item) else {
return
}
// remove item
self.rightBarButtonItems?.remove(at: itemIndex)
}
func addToRightBar(item: UIBarButtonItem) {
// make sure item is not present
guard self.rightBarButtonItems?.index(of: item) == nil else {
return
}
// add item
self.rightBarButtonItems?.append(item)
}
}
I used these methods like that
navigationItem.deleteFromRightBar(item: doneButton)
navigationItem.addToRightBar(item: editButton)

Resources