Displaying Instances of View Controllers Within Xcode Without Disturbing the Current Hierarchy

Displaying instances of view controllers within Xcode without disturbing the current hierarchy

A view controller can serve as a custom parent view controller of one or more child view controllers, displaying their views in any desired manner within its own view's interface.


You can instantiate a view controller in any desired manner. If you wish to pluck a view controller instance out of the storyboard (because you have already designed its view there), call:

https://developer.apple.com/documentation/uikit/uistoryboard/1616214-instantiateviewcontroller

Your view controller in the storyboard will need to have a storyboard ID string so that you can identify it.


After you've plucked a view controller from the storyboard in that way, to display its view in your interface, you must do the following dance:

  1. The parent calls https://developer.apple.com/documentation/uikit/uiviewcontroller/1621394-addchild

  2. The parent captures the view controller's view and sticks it into the interface as a subview of the parent's own view.

  3. The parent calls https://developer.apple.com/documentation/uikit/uiviewcontroller/1621405-didmove

The two view controllers now stand in a legal parent-child relationship and both view controllers will work properly. They can even refer to one another.

If you wish to remove the child view controller's view from the interface, you must reverse the dance:

  1. The parent calls https://developer.apple.com/documentation/uikit/uiviewcontroller/1621381-willmove with a nil parameter

  2. The parent removes the child view controller's view from the interface

  3. The parent calls https://developer.apple.com/documentation/uikit/uiviewcontroller/1621425-removefromparent


Please read "Implementing a Container View Controller" on the main view controller docs page:

https://developer.apple.com/documentation/uikit/uiviewcontroller

Memory management ARC and view controllers

You are not getting a leak because you create a new controller and ARC will release this allocation for you.

But, it's better to create a @property for your new view controller.
and modify your i.e. implementation like :

@property (nonatomic, strong) ViewController *myViewController;

if (!_myViewController)
self.myViewController = [[ViewController alloc] init];

self.myViewController.delegate = self;
[self presentViewController:_myViewController animated:YES completion:nil];

Here, you have a lazy property and you don't create a new one ViewController after the first creation.
But, you need to pass your delegate (or any property) outside your test.

Furthermore, if you use your first implementation and add this controller in a subview of the current controller without property, this will work but you will get a leak.
I got this experience with the code below :

RootViewController

- (void)viewDidLoad
{
[super viewDidLoad];

ViewController *myViewController = [[ViewController alloc] init];
[self.view addSubview:myViewController.view];
}

myViewController will be add on the screen but released immediately without keeping any reference of the object, so if you add an action in 'ViewController`, your application will crash without explanation of XCode.

So, the correct way to write this without leak will be :

- (void)viewDidLoad
{
[super viewDidLoad];

if (!_myViewController)
self.myViewController = [[ViewController alloc] init];

[self.view addSubview:self.myViewController.view];
}

The answer is a bit longer and can be improved so don't hesitate !
Hope it's going to help some people.

Missing transition from first view controller to second view controller using navigation controller

In the storyboard, make sure there is a rootViewController segue from the navigation controller to the first view controller. Also, make sure the navigation controller is marked as the initial view controller.

In the code, change

self.nvc?.pushViewController...

To

self.navigationController?.pushViewController...

Label keeps getting reset when changing View Controllers

Solution

A simple solution would be to override a method named viewDidLoad in your code and set the text of your questionLabel there.

override func viewDidLoad() {
super.viewDidLoad()

// You can use any index here. With little effort you can even randomize this.
let exampleIndex = 0

questionLabel.text = questions[exampleIndex]
}

Explanation

The problem with your original code is that you haven't set the text of the questionLabel programmatically when the main view appears. By only setting the text in the nextQuestionButton IBaction, your text label will change only if the action is called (which is triggered by pressing the next button I assume). Thus, the text you set from the storyboard was shown.

For a quick review of app view life cycle you can refer to this link.

Attempt to present UIViewController on UIViewController whose view is not in the window hierarchy

Where are you calling this method from? I had an issue where I was attempting to present a modal view controller within the viewDidLoad method. The solution for me was to move this call to the viewDidAppear: method.

My presumption is that the view controller's view is not in the window's view hierarchy at the point that it has been loaded (when the viewDidLoad message is sent), but it is in the window hierarchy after it has been presented (when the viewDidAppear: message is sent).


Caution

If you do make a call to presentViewController:animated:completion: in the viewDidAppear: you may run into an issue whereby the modal view controller is always being presented whenever the view controller's view appears (which makes sense!) and so the modal view controller being presented will never go away...

Maybe this isn't the best place to present the modal view controller, or perhaps some additional state needs to be kept which allows the presenting view controller to decide whether or not it should present the modal view controller immediately.

single function to dismiss all open view controllers

You can call :

self.view.window!.rootViewController?.dismiss(animated: false, completion: nil)

Should dismiss all view controllers above the root view controller.

displaying a ViewController from a non UI class

UINavigationController maintains a stack of view controllers. You can access this stack through the viewControllers property. To present your view controller, you can:

  • (a) have the navigation controller push the new view controller on to
    the stack (pushViewController:animated:);

  • (b) have the top view controller in the view controller stack present
    the new view controller modally (presentViewController:animated:completion:), or;

  • (c) add the new view controller to the view controller stack array
    manually by assigning a new viewControllers array to the navigation
    controller's viewControllers property (setViewControllers:).

Does a view controller get removed entirely when using a custom transition?

You said:

is my view controller still get removed after the transition finishes or is it still there but off the screen?

There are two completely separate issues here.

First, there is a question of the view controller hierarchy. When you present a new view controller, the old view controller is always kept in the view controller hierarchy so that when you dismiss back to it, it will still be there. However, when you dismiss, the dismissed view controller will be removed from the view controller hierarchy and (unless you do something unusual, like keeping your own strong reference to it somewhere) it will be deallocated.

Second, there is a separate question of the view hierarchy. When presenting, the UIPresentationController dictates whether the presenting view controller's view remains in the view hierarchy or not. By default, it keeps it in the view hierarchy, but generally if doing a modal, full-screen "present", you'd specify a UIPresentationController subclass that tells it to remove the presenting view controller's view when the transition is done.


For example, when doing a custom modal "present" transition where the presented view controller's view is opaque and covers the whole screen, then your UIViewControllerTransitioningDelegate would not only supply the animation controllers, but also specify a presentation controller:

func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return YourAnimationController(...)
}

func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return YourAnimationController(...)
}

func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return PresentationController(presentedViewController: presented, presenting: presenting)
}

And that presentation controller might be fairly minimal, only telling it to remove the presenter's view:

class PresentationController: UIPresentationController {
override var shouldRemovePresentersView: Bool { return true }
}


Related Topics



Leave a reply



Submit