0

I am having a bit of an issue with dismissing a modal view presented from a childviewController in a container view. I have a UINavigationController as the rootViewController (MainNavigationController), and present a modal from one of the childViewControllers from the selectedSegmentIndex 1 (secondViewController). The modal is presented fine, but when I dismiss the modal to go back to the secondViewController(a subclass of HomeController) it returns me back to selectedIndex 0, so not the selectedIndex 1 childViewController it was presented from. I would like the modal to dismiss and return the user back to childViewController it was presented from (the secondViewController) and not return back to selectedIndex 0. Thanks in advance!

// NavigationConroller as rootViewController

class MainNavigationController: UINavigationController {

    var segmentedController: UISegmentedControl!

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        let vc1 = TravelersFeedVC()
        let vc2 = ProfileVC()

        if isLoggedIn() {
            // assume user is logged in
            let homeController = HomeController()
            viewControllers = [homeController]
            homeController.firstViewController = vc1
            homeController.secondViewController = vc2

        } else {
            perform(#selector(showLoginController), with: nil, afterDelay: 0.01)
        }
    }

    fileprivate func isLoggedIn() ->  Bool {
        return UserDefaults.standard.isLoggedIn()
    }

    func showLoginController() {
        let loginController = LoginController()
        present(loginController, animated: true, completion: {
            // perhaps do something here later
        })
    }
}

// HomeController as parentViewController

class HomeController: UIViewController, FBSDKLoginButtonDelegate {

    // child view controllers to put inside content view
    var firstViewController: TravelersFeedVC?
    var secondViewController: ProfileVC?

    private var activeViewController: UIViewController? {
        didSet {
            removeInactiveViewController(inactiveViewController: oldValue)
            updateActiveViewController()
        }
    }

    private func removeInactiveViewController(inactiveViewController: UIViewController?) {
        if let inActiveVC = inactiveViewController {
            // call before removing child view controller's view from hierarchy
            inActiveVC.willMove(toParentViewController: nil)

            inActiveVC.view.removeFromSuperview()

            // call after removing child view controller's view from hierarchy
            inActiveVC.removeFromParentViewController()
        }
    }

    private func updateActiveViewController() {
        if let activeVC = activeViewController {
            // call before adding child view controller's view as subview
            addChildViewController(activeVC)

            activeVC.view.frame = contentView.bounds
            contentView.addSubview(activeVC.view)

            // call before adding child view controller's view as subview
            activeVC.didMove(toParentViewController: self)
        }
    }

    // UI elements
    lazy var contentView: UIView = {
        let tv = UIView()
        tv.backgroundColor = UIColor.purple
        tv.translatesAutoresizingMaskIntoConstraints = false
        tv.layer.masksToBounds = true
        return tv
    }()


    var segmentedController: UISegmentedControl!

    override func viewDidLoad() {
        super.viewDidLoad()

        activeViewController = firstViewController

        checkIfUserIsLoggedIn()

        view.addSubview(contentView)

        setupProfileScreen()

        let items = ["Travelers", "Me"]
        segmentedController = UISegmentedControl(items: items)
        navigationItem.titleView = segmentedController

        segmentedController.tintColor = UIColor.black
        segmentedController.selectedSegmentIndex = 0

        // Add function to handle Value Changed events
        segmentedController.addTarget(self, action: #selector(HomeController.segmentedValueChanged(_:)), for: .valueChanged)

        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Sign Out", style: .plain, target: self, action: #selector(handleSignOut))
        navigationItem.leftBarButtonItem?.tintColor = UIColor.black

    }


    // reference to collectionViewController
    var travelersFeedVC: TravelersFeedVC!

    func segmentedValueChanged(_ sender:UISegmentedControl!)
    {
        switch segmentedController.selectedSegmentIndex {
        case 0:
            activeViewController = firstViewController

        case 1:
            activeViewController = secondViewController

        default: // Do nothing
            break
        }
    }

// secondViewcontroller in containerView where modal is presented from

class ProfileVC: UIViewController {

// button to present modal
    lazy var placesButton: UIButton = {
        let customButton = UIButton(type: .system)
        customButton.backgroundColor = UIColor.clear
//        customButton.frame = CGRect(x: 150, y: 50, width: 120, height: self.view.frame.height)
        customButton.setTitle("## of Places", for: .normal)
        customButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
        customButton.setTitleColor(.white, for: .normal)
        customButton.addTarget(self, action: #selector(handleShowPlacesVC), for: .touchUpInside)

        return customButton
    }()

// function to call to present modal
    func handleShowPlacesVC() {
        let placesVC = PlacesTableVC()
        let navigationController = UINavigationController(rootViewController: placesVC)
        present(navigationController, animated: true, completion: nil)
    }

// modal view to dismiss

   override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "back", style: .plain, target: self, action: #selector(handleCancel))

    }

// dismiss modal view to return to secondViewController in childViewController containerView
    func handleCancel() {
        dismiss(animated: true, completion: nil)
    }
user3708224
  • 1,229
  • 4
  • 19
  • 37

2 Answers2

0

When closing the modal dialog the viewDidAppear function in MainNavigationController is called. There you set a new homeController with it's childs. This will trigger a viewDidload in the HomeController with setting of firstViewController. Try to set a breakpoint there and you will see it.

I suggest to avoid content creation in viewDidAppear, use viewDidLoad instead.

Another hint: 'dismiss' is defined as: 'Dismisses the view controller that was presented modally by the view controller.' - If you open for instance an alert above your modal vc it closes the alert, not the modal view (self). A correct implementation has to call dismiss on the presenting controller (same controller that opened it): "presentingViewController?.dismiss()" It works in your code because apple has implemented a fallback for the case that nothing is presented, but it's a trap that cause some headache sometime.

ObjectAlchemist
  • 1,109
  • 1
  • 9
  • 18
  • You should try to fix this problem correctly instead of a follow up caused by a workaround. viewDidAppear is called every time the view appears again. Moving the app to background and return to foreground for instance calls that method. It doesn't sound right to manipulate view hierarchy in that cases. – ObjectAlchemist Apr 16 '17 at 18:57
  • So I can not figure out how to solve the blank screen the first time logging into the app when using viewDidLoad. The viewDidLoad did solve my navigation problems, but now I have the blank screen first time login as before...any suggestions of direction? – user3708224 Apr 17 '17 at 00:29
  • Have you already used the debugger? Set a breakpoint in viewDidLoad of your controllers and check loading order. I think one of the controller is nil while usage, but can't say it exactly. – ObjectAlchemist Apr 17 '17 at 09:06
0

The chances are that although you're calling present from the child view controller, it isn't in fact handling the presentation. From the Apple docs:

The object on which you call this method may not always be the one that handles the presentation. Each presentation style has different rules governing its behavior. For example, a full-screen presentation must be made by a view controller that itself covers the entire screen. If the current view controller is unable to fulfill a request, it forwards the request up the view controller hierarchy to its nearest parent, which can then handle or forward the request.

Since you're keeping a reference of the active view controller, one solution may be to explicitly set the index upon dismissal.

sooper
  • 5,991
  • 6
  • 40
  • 65