Search code examples
iosswiftxcodeuicontainerview

Why are the components in the view in my container not usable?


While working through a Udemy course, I'm trying to make a simple log in / sign up interface. In my first view controller I have a vertical stack view containing a label, a segmented control, and a container view. The segmented control can be either "Log In" or "Sign Up". I want to load the appropriate view controller when the segmented control changes.

Top Level View Controller

My LogInViewController has a vertical stack view with a text field for username, another for password, and a Log In button. My SignUpViewController has a vertical stack view with text fields for email address, username, password, and password confirmation followed by a Sign Up button.

My first view controller looks like this:

import UIKit

class ViewController: UIViewController, LogInViewDelegate, SignUpViewDelegate
{
    @IBOutlet weak var logInSignUpControl: UISegmentedControl!
    @IBOutlet weak var containerView: UIView!

    var logInVC: LogInViewController?
    var signUpVC: SignUpViewController?
    var activeVC = 0

    override func viewDidLoad()
    {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        initializeCustomControllers()
    }

    func initializeCustomControllers()
    {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)

        logInVC = storyboard.instantiateViewController(withIdentifier: "LogInViewController") as? LogInViewController
        signUpVC = storyboard.instantiateViewController(withIdentifier: "SignUpViewController") as? SignUpViewController

        logInVC?.delegate = self
        signUpVC?.delegate = self

        logInVC?.willMove(toParent: self)
        logInVC?.view.frame = containerView.bounds
        containerView.addSubview(logInVC!.view)
        addChild(logInVC!)
        logInVC?.didMove(toParent: self)
    }

    func swapCustomController(from: UIViewController,to: UIViewController)
    {
        from.view.removeFromSuperview()
        from.removeFromParent()

        to.willMove(toParent: self)
        to.view.frame = containerView.bounds
        containerView.addSubview(to.view)
        addChild(to)
        to.didMove(toParent: self)
     }

    @IBAction func logInSignUpControlTapped(_ sender: Any)
    {
        switch logInSignUpControl.selectedSegmentIndex
        {
        case 0:
            if activeVC == 1
            {
                swapCustomController(from: signUpVC!, to: logInVC!)
                activeVC = 0
            }
        case 1:
            if activeVC == 0
            {
                swapCustomController(from: logInVC!, to: signUpVC!)
                activeVC = 1
            }
        default:
            break
        }
    }

    func logInAttempted(error: Error?)
    {
        if error == nil
        {
            // segue
            print("log in successful")
        }
        else
        {
            showAlert(title: "Log In error",
                      message: error?.localizedDescription ?? "Unknown Error")
        }
    }

    func signUpAttempted(error: Error?)
    {
        showAlert(title: "Sign Up Error",
                  message: error?.localizedDescription ?? "Unknown Error")
    }
}

The problem is that, when either the log in or sign up views are loaded into the container view, the fields and buttons are not usable from the simulator. I can't tap into the text fields and the buttons are unresponsive.

I tried adding a call to makeFirstResponder() to the first text field, which allowed me to enter text, but the buttons are still unusable.

Thanks in advance for any help.


Solution

  • The main issue here is probably in the words "vertical stack view". A stack view can operate correctly only if it is sized by autolayout constraints. Yours might not be correctly pinned to its superview.

    Also, I don't see any code where you grapple with the possibility that the container view might be resized after you assign its bounds to the frame of the child view.


    Also, this might not matter here, but in general please note that you are doing this dance incorrectly:

        logInVC?.willMove(toParent: self)
        logInVC?.view.frame = containerView.bounds
        containerView.addSubview(logInVC!.view)
        addChild(logInVC!)
        logInVC?.didMove(toParent: self)
    

    The correct commands in the correct order is:

        addChild(logInVC!)
        logInVC!.view.frame = containerView.bounds
        containerView.addSubview(logInVC!.view)
        logInVC!.didMove(toParent: self)
    

    In general problems with a child view controller view not responding, it is either because the outlets are not hooked up or because the view controller has been allowed to fall on the floor leaving the view with no one to talk to.

    However, in your case it might well be because the buttons are ending up outside the bounds of their superview because the stack view is not configured correctly. (You can use the View Debugger to confirm that hypothesis.) A view outside of its superview is visible but not touchable.