Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Programmatically change focus after input text in UITextfield

I have a simple view which has two textviews as well as a button. All controls are positioned vertically. I can navigate from one control to another by using the remote. However, I'm trying to get the following scenario working:

  1. The user launches the app (focus is on first textfield)
  2. The user enters the textfield and uses the fullscreen keyboard, then she hits the "Next" button
  3. The app shows the second textfield (fullscreen keyboard), types in some text and hits the "Done" button
  4. The app leaves the fullscreen keyboard and focuses the button

While step 1-3 is working out of the box, changing the focus programatically (4) doesn't work. Here's some code:

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    if(textField == self.textfieldB) {
        self.activeView = self.textfieldB;
        [self setNeedsFocusUpdate];
        [self updateFocusIfNeeded];
        return YES;
    } 
    return NO;
}

and in - (UIView *)preferredFocusedView I'm returning whatever is stored in self.activeView (yes, it will return the button instance). All methods are called as expected, however the focus doesn't change. What am I doing wrong here?

Thanks in advance!

like image 299
Chrizzor Avatar asked Oct 30 '22 11:10

Chrizzor


2 Answers

According to this developer forums thread the behaviour is intentional:

When dismissing a presented view controller, like a fullscreen keyboard, UIKit will automatically return focus back to where it was when that view controller was first presented, if that view is still focusable. This is so that focus automatically returns back to where the user would expect it to be. There is currently no mechanism for overriding this behavior.

In a similar case I switched the focus with a delay after returning from the keyboard screen. It’s an ugly hack, I wish UIKit wasn’t so opinionated about this.

like image 82
zoul Avatar answered Nov 12 '22 18:11

zoul


So none of the suggestions I came across really worked for me, including the newer restoresFocusAfterTransition being set to false.

In addition to this, the only thing that worked was building on 6rchid's answer with a delay to take into account the focus engine updates.

class EmailViewController: UIViewController {

    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var submitButton: UIButton!

    private var focusedEnvironments: [UIFocusEnvironment] = [UIFocusEnvironment]()

    override var preferredFocusEnvironments: [UIFocusEnvironment] {
        return focusedEnvironments
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        restoresFocusAfterTransition = false

        focusedEnvironments = [emailTextField, submitButton]
    }

}

extension EmailViewController: UITextFieldDelegate {

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

        emailTextField.isEnabled = false
        focusedEnvironments = [submitEmailButton]

        submitButton.setNeedsFocusUpdate()

        UIView.transition(with: submitEmailButton, duration: 1.0, options: .transitionCrossDissolve, animations: {
            self.submitButton.isEnabled = true
        }) { (_) in
            // re-enable this once focus updates have completed
            self.emailTextField.isEnabled = true
        }
        return true
    }
}

If the UITextField is left enabled before the focus updates complete, the focus seems to always want to reset to the UITextField. So the trick is to disable it, then re-enable it when you want.

like image 28
PostCodeism Avatar answered Nov 12 '22 17:11

PostCodeism