I'm reading a book that states that source view controller is not necessarily a presenting view controller. The source VC is the one that calls presentViewController:... method, and the presenting VC(it's view) is the one that gets obscured by a presented VC view. I can't think of a single example in which the presenting VC is not the same as source VC. Please provide some. Thanks
Text from book:
“Original presenter:
The view controller to which presentViewController:animated:completion: was sent. Apple sometimes refers to this view controller as the source; “original presenter” is my own term.
The presented view controller is set as the original presenter’s presentedViewController.
Presenting view controller:
The presented view controller’s presentingViewController. This is the view controller whose view is replaced or covered by the presented view controller’s view. By default, it is the view controller whose view is the entire interface — namely, either the root view controller or an already existing presented view controller. It might not be the same as the original presenter.
The presented view controller is set as the presenting view controller’s presentedViewController. Thus, the presented view controller might be the presentedViewController of two different view controllers.”
I was asking myself the same question when learning about view controller transitions, specifically when trying to understand the animationControllerForPresentedController:presentingController:sourceController: method from the UIViewControllerTransitioningDelegate protocol. The struggle was about the difference between the presentingController and sourceController arguments.
I found the answer in the View Controller Programming Guide. You can find the following in the Presenting View Controllers Modally section:
The view controller that calls the
presentViewController:animated:completion: method may not be the one
that actually performs the modal presentation. The presentation style
determines how that view controller is to be presented, including the
characteristics required of the presenting view controller. For
example, a full-screen presentation must be initiated by a full-screen
view controller. If the current presenting view controller is not
suitable, UIKit walks the view controller hierarchy until it finds one
that is. Upon completion of a modal presentation, UIKit updates the
presentingViewController and presentedViewController properties of the
affected view controllers.
This means that the 'source' view controller concept is not something randomly created by the author of the book you mentioned.
I'm afraid the book you're reading may have made a complex subject even more complex by adding the so-called Source View Controller to the whole mix of names.
For starters, there is no such concept of a "Source" in View Controllers. You have a parentViewController and childViewControllers only when talking about Container View Controllers. And you have a presentingViewController and a presentedViewController only when talking about presenting View Controllers modally.
You also have View Controllers whose main purpose is to manage other View Controllers, namely the Navigation Controller, the Tab Bar Controller, the Split View Controller and the Popover Presentation Controller. So any given View Controller may query itself to know if it is "attached" to a Navigation Controller, for instance.
What I'm guessing your book is implying is that a Source VC would be the one that makes another VC appear on screen. In this sense, it is right. The "source" may be different than the presenting VC. Take this snippet:
[self presentViewController:aViewController animated:YES completion:nil];
In the example above, the presenting VC is also the "source" VC. But here:
[someViewController presentViewController:anotherViewController animated:YES completion:nil];
We are calling the method on some VC lying somewhere passing yet another VC as argument. So in this case the presenting VC is someViewController and the presented VC is anotherViewController. Should this line of code be inside a third VC, then said third VC would be the "source".
But that's the thing! This last snippet of code may not even be inside a View Controller in the first place, so it's a bit odd to think about "source VCs".
Related
On my main view in a NavigationController I have a button that brings up a new view modally to submit a post. I have a button to dismiss this view which brings the user back to the main view, however if the users proceeds to make a post, I want it to dismiss the view and proceed to a show the post in the Navigation controller.
The ideal effect would have the standard navigation controller's back button on the top that can bring the user back to the main view when they have finished looking at their post.
I have tried several different methods, but I tend to get an error stating "Warning: Attempt to present (The View I Want to Show) whose view is not in the window hierarchy!
Thanks for any info!
Yes, it's possible. This is the best way I've found to structure the app to allow this functionality.
RootViewController
UINavigationViewController
ContentViewController (your current "main" view controller)
You're basically going to create a new RootViewController that is going to contain your current UINavigationController as a child. I usually setup the window.rootViewController programmatically in the AppDelegate. I would also keep a global reference to it.
Have a look at Apple's documentation on this. Basically, the RootViewController code would look like this:
[self addChildViewController:navController];
navController.view.frame = self.view.bounds
[self.view addSubview:self.navController.view];
[navController didMoveToParentViewController:self];
Now, whenever you need to present a modal view controller, present it from the RootViewController, instead of the current/top view controller on the UINavigationBar. This allows you to manipulate the UINavigtaionController independently and you won't get the error you're seeing.
So present the modal view controller from the RootViewController, this covers the UINavigationController and its content (top view controller). Make any changes you need to the UINavigationController stack (push, pop, etc...) with no animation. When you're done dismiss the modal view controller (with animation) and it will show you're adjusted UINavigationController.
Hopefully, this all makes sense. It's kind of complex and hard to explain in text. :p
If you don't want to setup a RootViewController, you might want to try presenting the modal view controller from the UINavigationController, as that might have the same effect. From the view controller just do self.parentViewController.present.
The main thing you're trying to avoid is presenting a modal from a view controller and then removing that view controller (pop) before the modal is dismissed. The view controller that presents the view controller is responsible for dismissing it.
I finally got it all working (I'm still new to Swift) and the two comments on the question were very helpful. The most helpful of all was the top answer here's example on github.
Similar question
I wanted to ask who should be the one dismissing a presented view controller?
Lets say I presented a view controller and on an IBAction in that view controller, I want to dismiss it.
Should I be passing that responsibility to the presenting view controller by creating a delegate method or I should be just calling the dismissViewController:animated: on itself, which inturn anyways asks its presenting view controller to dismiss the presented view controller?
So, I think these are some clear cut cases where the presenting view controller should be the one dismissing the presented view controller
The presented view controller is passing some data back to the presenting view controller.
The presenting view controller wants to do something after the dismissal of the presented view controller.
The presenting view controller to handle how the dismissal is going to happen, does it need some kind of animation
What if the presented view controller first checks if the presenting view controller actually wants to take the responsibility of dismissal by checking the presenting view controller implemented the dismissal delegate method?
Is it really worth putting the complexity of conditional logic here?
And yes, I tried reading it on other forums and questions like
Dismissing a Presented View Controller
Dismissing Modal View Controllers
Present and dismiss modal view controller
view controllers: presentation, dismissal
But couldn't really find the right logical answer.
Read this below link. You will get the idea how animation and presentation take place between viewcontrollers.
https://www.raywenderlich.com/113845/ios-animation-tutorial-custom-view-controller-presentation-transitions
What happens with presenting viewController after it presents a presented viewController. Does its instance live forward? Is it destroyed when it is obscured by presented viewController views? If it is destroyed, how do I tell the system not to destroy it, but keep it intact?
I'm designing a game in witch the game is obscured with a presented viewController when some button is clicked. When the player returns to the game, I want it to restore its state exactly as it was before presenting a viewController
The presenting view controller keeps it's instance, and control is returned to it after the presented view controller is dismissed. In fact, you should have the presenting view controller dismiss the presented controller if possible.
Since the instance of UIViewControllers stay intact when presenting (or pushing) another view controller, it is best practice to implement the didReceiveMemoryWarning method in your UIViewControllers to release any memory that you can (clear caches, etc). Any data you clear out you may want to re-populate in the viewWillAppear method.
Other info from the View Controller Programming Guide for iOS :
Support for presenting view controllers is built in to the UIViewController class and is available to all view controller objects. You can present any view controller from any other view controller, although UIKit might reroute the request to a different view controller. Presenting a view controller creates a relationship between the original view controller, known as the presenting view controller, and the new view controller to be displayed, known as the presented view controller. This relationship forms part of the view controller hierarchy and remains in place until the presented view controller is dismissed.
I am confused about "In which situations we have to use presentViewController and UINavigationController".
I had read so many documents, but I haven't found accurate explanation. We can always use UInavigationController then what is the use of presentViewController ?
Thanks.
UINavigationController maintains the stack of the controllers that are being viewed. So once you push through 1->2->3 view controllers then you can pop in 3->2->1 manner. Unless you don't change the stack this kind of flow is maintained by UINavigationController. Now lets say you want to show 4th view controller without disturbing the above flow. Then you can use presentViewController.
This is the simplest and basic is for using navigation controller and presentViewController
You can't push a navigation controller into other navigation controller.
You can present a navigation controller above other navigation controller.
If you push a view controller into the navigation controller, view controller's view cover only area inside navigation controller.
If you present a view controller, view controller's view cover the window hierarchy (user can't interact with other parts of the application).
You can don't use UINavigationController to present some UIViewController. You should care about "close" button to let user to close presented UIViewController
I've create test project to illustrate my answer https://github.com/K-Be/PresentTest
Is there a way to know if a UIViewController has been presented and dismissed modally ?
Something like:
hasBeenPresentedModally
hasBeenDismissedModally
thanks
There's nothing built in, but a view controller could, upon receiving viewDidAppear and/or viewWillDisappear check whether it has a parentViewController, since per Apple's documentation (emphasis added):
Parent view controllers are relevant in navigation, tab bar, and modal
view controller hierarchies. In each of these hierarchies, the parent
is the object responsible for displaying the current view controller.
If you are using a view controller as a standalone object—that is, not
as part of a view controller hierarchy—the value in this property is
nil.
If it has then it can set suitable flags for future reference.
Note that being presented modally is different from being truly modal. For example, on an iPad you might put one controller inside a UIPopoverController, so that controller isn't presented modally, but then it might modally present another controller on top of itself. So the second controller is presented modally but isn't itself a modal dialogue because — if the program is otherwise set up suitably — the user can just ignore the popover entirely.
Check if your UIViewController's parentViewController property is nil or not.
If the property is nil then it's dismissed otherwise it's presented.
NOTE: UITableViewController's childViewController's parentViewController property would also be not nil, you should also make sure the parentViewController is not UITableViewController.