0

I'm creating a Sign Up wizard with multiple UIViewControllers.

I currently have it setup so a user enter's his email, clicks the "Go" button on the keyboard and the next UIViewController slides in from the right where the user will enter his name. The problem though is that when I call presentViewController to bring the next UIViewController in, it dismisses the keyboard.

I'd like the keyboard to stay open the entire time while switching ViewControllers. If you look at Facebook's iOS app, they do what I'm trying to do with their signup page.

Any help or suggestions will be greatly appreciated. I read something about using an overlay window, but am not sure how to go about it since I will have multiple UIViewController's in my Sign Up wizard.

Here's my initial controller in the Sign Up Wizard:

class SignUpEmailViewController: UIViewController {
    var titleLabel = UILabel.newAutoLayoutView()
    var emailField = SignUpTextField(placeholder: "Enter your email address")
    var emailLabel = UILabel.newAutoLayoutView()
    var continueButton = SignUpContinueButton.newAutoLayoutView()
    var footerView = SignUpFooterView.newAutoLayoutView()

    let presentAnimationController = PushInFromLeftAnimationController()
    let dismissAnimationController = PushInFromRightAnimationController()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupViews()
        setupGestures()
    }

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        emailField.becomeFirstResponder()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func setupViews() {
        view.backgroundColor = UIColor.colorFromCode(0xe9eaed)

        titleLabel.text = "Get Started"
        titleLabel.font = UIFont(name: "AvenirNextLTPro-Demi", size: 18)
        titleLabel.textColor = Application.greenColor

        emailField.enablesReturnKeyAutomatically = true
        emailField.returnKeyType = .Go
        emailField.delegate = self

        emailLabel.text = "You'll use this email when you log in and if you ever need to reset your password."
        emailLabel.font = UIFont(name: "AvenirNextLTPro-Regular", size: 13)
        emailLabel.textColor = .colorFromCode(0x4e5665)
        emailLabel.numberOfLines = 0
        emailLabel.textAlignment = .Center

        continueButton.addTarget(self, action: "continueButtonPressed", forControlEvents: .TouchUpInside)
        continueButton.hidden = true

        view.addSubview(titleLabel)
        view.addSubview(emailField)
        view.addSubview(emailLabel)
        view.addSubview(continueButton)
        view.addSubview(footerView)
        setupConstraints()
    }

    func setupGestures() {
        let gestureRecognizer = UISwipeGestureRecognizer(target: self, action: "swipeHandler")
        gestureRecognizer.direction = .Down
        view.addGestureRecognizer(gestureRecognizer)

        let tapGesture = UITapGestureRecognizer(target: self, action: "dismissKeyboard")
        view.addGestureRecognizer(tapGesture)
    }

    func setupConstraints() {
        titleLabel.autoAlignAxisToSuperviewAxis(.Vertical)
        titleLabel.autoPinEdgeToSuperviewEdge(.Top, withInset: screenSize.height * 0.2)

        emailField.autoAlignAxisToSuperviewAxis(.Vertical)
        emailField.autoPinEdge(.Top, toEdge: .Bottom, ofView: titleLabel, withOffset: 15)
        emailField.autoSetDimensionsToSize(CGSize(width: screenSize.width * 0.85, height: 40))

        emailLabel.autoAlignAxisToSuperviewAxis(.Vertical)
        emailLabel.autoPinEdge(.Top, toEdge: .Bottom, ofView: emailField, withOffset: 10)
        emailLabel.autoSetDimension(.Width, toSize: screenSize.width * 0.85)

        continueButton.autoAlignAxisToSuperviewAxis(.Vertical)
        continueButton.autoPinEdge(.Top, toEdge: .Bottom, ofView: emailField, withOffset: 10)
        continueButton.autoSetDimensionsToSize(CGSize(width: screenSize.width * 0.85, height: 30))

        footerView.autoSetDimension(.Height, toSize: 44)
        footerView.autoPinEdgeToSuperviewEdge(.Bottom)
        footerView.autoPinEdgeToSuperviewEdge(.Leading)
        footerView.autoPinEdgeToSuperviewEdge(.Trailing)
    }

    override func prefersStatusBarHidden() -> Bool {
        return true
    }

    func swipeHandler() {
        dismissViewControllerAnimated(true, completion: nil)
    }

    func continueButtonPressed() {
        presentNextViewController()
    }

    func dismissKeyboard() {
        view.endEditing(true)
    }

    func presentNextViewController() {
        let toViewController = SignUpNameViewController()
        toViewController.transitioningDelegate = self
        toViewController.firstNameField.becomeFirstResponder()
        presentViewController(toViewController, animated: true, completion: nil)
    }
}

extension SignUpEmailViewController: UIViewControllerTransitioningDelegate {
    func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return presentAnimationController
    }

    func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return dismissAnimationController
    }
}

extension SignUpEmailViewController: UITextFieldDelegate {
    func textFieldShouldReturn(textField: UITextField) -> Bool {
        presentNextViewController()
        return true
    }

    func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
        continueButton.hidden = true
        emailLabel.hidden = false
        return true
    }

    func textFieldShouldEndEditing(textField: UITextField) -> Bool {
        continueButton.hidden = false
        emailLabel.hidden = true
        return true
    }
}

And here's the controller that I'm trying to present:

class SignUpNameViewController: UIViewController, UIViewControllerTransitioningDelegate {
    var titleLabel = UILabel.newAutoLayoutView()
    var textFieldContainer = UIView.newAutoLayoutView()
    var firstNameField = SignUpTextField(placeholder: "First name")
    var lastNameField = SignUpTextField(placeholder: "Last name")
    var continueButton = SignUpContinueButton.newAutoLayoutView()
    var footerView = SignUpFooterView.newAutoLayoutView()

    let presentAnimationController = PushInFromLeftAnimationController()
    let dismissAnimationController = PushInFromRightAnimationController()

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        firstNameField.becomeFirstResponder()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        setupViews()
        setupGestures()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func setupViews() {
        view.backgroundColor = UIColor.colorFromCode(0xe9eaed)

        titleLabel.text = "What's your name?"
        titleLabel.font = UIFont(name: "AvenirNextLTPro-Demi", size: 18)
        titleLabel.textColor = Application.greenColor

        firstNameField.returnKeyType = .Next
        firstNameField.enablesReturnKeyAutomatically = true

        lastNameField.returnKeyType = .Next
        lastNameField.enablesReturnKeyAutomatically = true

        continueButton.addTarget(self, action: "continueButtonPressed", forControlEvents: .TouchUpInside)

        view.addSubview(titleLabel)
        view.addSubview(textFieldContainer)
        textFieldContainer.addSubview(firstNameField)
        textFieldContainer.addSubview(lastNameField)
        view.addSubview(continueButton)
        view.addSubview(footerView)
        setupConstraints()
    }

    func setupGestures() {
        let gestureRecognizer = UISwipeGestureRecognizer(target: self, action: "swipeHandler")
        gestureRecognizer.direction = .Right
        view.addGestureRecognizer(gestureRecognizer)

        let tapGesture = UITapGestureRecognizer(target: self, action: "dismissKeyboard")
        view.addGestureRecognizer(tapGesture)
    }

    func setupConstraints() {
        titleLabel.autoAlignAxisToSuperviewAxis(.Vertical)
        titleLabel.autoPinEdgeToSuperviewEdge(.Top, withInset: screenSize.height * 0.2)

        textFieldContainer.autoPinEdge(.Top, toEdge: .Bottom, ofView: titleLabel, withOffset: 15)
        textFieldContainer.autoAlignAxisToSuperviewAxis(.Vertical)
        textFieldContainer.autoSetDimensionsToSize(CGSize(width: screenSize.width * 0.8, height: 40))

        let spaceBetweenTextFields: CGFloat = 5
        let textFieldSize = ((screenSize.width * 0.8) - spaceBetweenTextFields) / 2
        let textFields: NSArray = [firstNameField, lastNameField]
        textFields.autoDistributeViewsAlongAxis(.Horizontal, alignedTo: .Horizontal, withFixedSize: textFieldSize, insetSpacing: false)

        firstNameField.autoPinEdgeToSuperviewEdge(.Top)
        firstNameField.autoPinEdgeToSuperviewEdge(.Bottom)

        lastNameField.autoPinEdgeToSuperviewEdge(.Top)
        lastNameField.autoPinEdgeToSuperviewEdge(.Bottom)

        continueButton.autoAlignAxisToSuperviewAxis(.Vertical)
        continueButton.autoPinEdge(.Top, toEdge: .Bottom, ofView: textFieldContainer, withOffset: 10)
        continueButton.autoSetDimensionsToSize(CGSize(width: screenSize.width * 0.8, height: 30))

        footerView.autoSetDimension(.Height, toSize: 44)
        footerView.autoPinEdgeToSuperviewEdge(.Bottom)
        footerView.autoPinEdgeToSuperviewEdge(.Leading)
        footerView.autoPinEdgeToSuperviewEdge(.Trailing)
    }

    override func prefersStatusBarHidden() -> Bool {
        return true
    }

    func dismissKeyboard() {
        view.endEditing(true)
    }

    func swipeHandler() {
        dismissViewControllerAnimated(true, completion: nil)
    }

    func continueButtonPressed() {
        let toViewController = SignUpPasswordViewController()
        toViewController.transitioningDelegate = self
        presentViewController(toViewController, animated: true, completion: {})
    }

    func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return presentAnimationController
    }

    func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return dismissAnimationController
    }
}

And here is my custom transition:

class PushInFromLeftAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
        return 0.35
    }

    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
        let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
        let finalFrameForVC = transitionContext.finalFrameForViewController(toViewController)
        let containerView = transitionContext.containerView()
        let bounds = UIScreen.mainScreen().bounds
        toViewController.view.frame = CGRectOffset(finalFrameForVC, bounds.size.width, 0)
        containerView!.addSubview(toViewController.view)

        UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: {
            fromViewController.view.frame = CGRectOffset(finalFrameForVC, -bounds.size.width, 0)
            toViewController.view.frame = finalFrameForVC
            }, completion: {
                finished in
                transitionContext.completeTransition(true)
        })
    }
}
Thomas
  • 2,356
  • 7
  • 23
  • 59
  • See my comments on this question: http://stackoverflow.com/q/33127863/341994 – matt Oct 14 '15 at 23:14
  • Hey @matt, I checked your answer and updated my question to show my code. Any idea how facebook seems to keep the keyboard open as they switch from ViewController to ViewController in their Sign Up Wizard? Maybe their not actually switching view controllers, but instead, just animating the TextFields sliding in? – Thomas Oct 14 '15 at 23:32
  • Or is the only solution to add a UITextField to the window that gets the role of first responder? That seems kind of messy – Thomas Oct 14 '15 at 23:34
  • The thing is, as we discussed in the other comments, that only a view that is in the view hierarchy can be first responder. Maybe you can solve this by doing a custom transition animation, because that way you are in complete control over the views throughout the process. Looks like you may be already doing that, actually. – matt Oct 14 '15 at 23:53
  • That's what I thought too @matt. I updated my question to show my custom transition. I'm not ever removing the fromViewController's view from the view hierarchy, I'm just sliding it off the screen which is why I'm unsure why the keyboard is still being dismissed. – Thomas Oct 15 '15 at 00:01
  • Okay, cool, so what I'm wondering is: when you say `addSubview` for the new vc's view, what if you make the text field the first responder _right then_? I've never tried this but it's worth the experiment. – matt Oct 15 '15 at 00:02
  • Unfortunately no luck. The keyboard still moves down a little like it's being dismissed, and the quickly pops back up to full size – Thomas Oct 15 '15 at 00:08
  • What could possibly be dismissing the keyboard? – Thomas Oct 15 '15 at 00:10
  • And what's weird, is that when I call dismissViewController, they keyboard isn't dismissed – Thomas Oct 15 '15 at 00:12
  • Is this a fullscreen presentation? If so, what if you make it an overFullScreen presentation, so that the old vc.'s view is never removed? – matt Oct 15 '15 at 00:39

1 Answers1

0

I think the keyboard is dismissed because the corresponding UIResponder will resign at the same time the ViewController will disappear.

Have you tried setting the next UITextField (in the next ViewController) as the first responder then? thus the keyboard will be linked to a new UIResponder before the previous one would end editing...

Alde
  • 146
  • 6
  • yeah I tried setting toViewController.firstNameField.becomeFirstResponder() before calling presentViewController(), and what happens though is the keyboard moves down a little like it's resigning, and then moves back up to its original position – Thomas Oct 14 '15 at 23:17
  • mmm... yes I was expecting something like this... and I wonder how much this can be avoided by wisely choosing the order that the operations will occur... because the other solution I was having in mind is kinda ugly: having a temporary uitextfield behind the keyboard that gets the role of the first responder while the viewcontroller are going in and out. – Alde Oct 14 '15 at 23:28
  • Yeah I'm starting to think that might be the only solution – Thomas Oct 14 '15 at 23:37
  • How would I go about adding the temporary text field? Would I add it as a subview of a UIWindow() object? – Thomas Oct 14 '15 at 23:54