apple documentation on UIViewController dismiss(animated:completion:) says that
The presenting view controller is responsible for dismissing the view
controller it presented. If you call this method on the presented view
controller itself, UIKit asks the presenting view controller to handle
the dismissal.
but when I override the presenting controller's dismiss
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil)
it is not called
ViewController1 -> present ViewController2
ViewController2.dismiss -> dismiss from ViewController2 is called and ViewController1.(override)dismiss is not called
According to the documentation of present(_:animated:completion:):
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.
In case of iPhone (horizontally compact environment) it uses FullScreen mode by default. So here ViewController1 may not be the 'presentingViewController'.
[Spoiler - Work-around]
I had a similar issue with a modal vc and ended up working things out differently.
My modal style was initially:
modalVC.modalPresentationStyle = .overCurrentContext
and changing that to
modalVC.modalPresentationStyle = .fullScreen
triggers the life cycle calls to fire on the presentingVC when I dismiss the modal so that did the trick for me. I can now make sure code runs whenever the modal gets dismissed without having to bother with delegation etc...
I hope that can help someone too in the future.
Related
I have a container UIViewController that hosts a single UINavigationController. The container view controller has a button that opens a new view controller by calling present(newViewController, animated: true, completion: nil).
The newViewController has its own UINavigationController and also contains a button. That button can present another view controller that itself has a UINavigationController and another button and so on.
I want to keep that pattern going for as many iterations as possible and save the states of all of them. Is that possible? To close the current view controller I call _ = navigationController?.popViewController(animated: true) but that erases all the data from the previous view controller as well.
You asked two questions
1) want to keep that pattern going for as many iterations as possible and save the states of all of them.
Ans. : you can not continuously presenting view on already presented view controller. for that first you need to dismiss previous presented view.
2) To close the current view controller I call
_ = navigationController?.popViewController(animated: true)
but that erases all the data from the previous view controller as well.
Ans. If you present any view controller then don't use popviewcontroller but use dismisViewController.
Apologies as I know there are some similar questions, but I've been looking for two weeks through every one I can find, and cannot figure it out (I'm a bit of a novice).
I have a few different View Controllers, not using a Navigation Controller. I can segue between them no problem. The issue is, I need each view to be dismissed when I segue to a new one. Here is some of what I've tried so far.
Option 1 (in new View Controller)
override func viewDidAppear(_ animated: Bool) {
presentingViewController?.dismiss(animated: false, completion: nil)
}
Option 2 (in old View Controller)
override func viewDidDisappear(_ animated: Bool) {
self.dismiss(animated: false, completion: nil)
}
In both of these cases, the new view gets dismissed and I'm taken back to the old view. I've tried about 20 versions of similar code.
Should I be using the first VC in my program as my "main" view controller, and presenting/dismissing all others on top of it? I didn't think this approach seemed memory efficient, when the "main" VC is not often used after initially loading the app.
It seems like I'm missing or not understanding something. Any help would be greatly appreciated.
Think of it this way: A view controller can't exist on an island. It has to be presented on top of something.
That means when you present one VC on top of another, the presenting view controller is the "foundation" for the new one you just presented.
If you don't want to present VCs on top of each other, you have a couple of options:
1) Use a navigation controller. This is probably the best approach. You can present or push any view controller. If you decide to push, you can remove the old one from the navigation stack, or you can keep it there so the user can go back. There are lots of ways to use a navigation controller, and it's easily the most flexible way to navigate between controllers.
2) Use a tab bar controller. This works best if you have just a few different view controllers in your app, but it's good for certain use cases.
3) Do exactly what you said in your post (use the root view controller to present/dismiss all other VCs). As I said, you can't present a view controller out of thin air-- there always has to be something behind it. Unless there's a ton of stuff going on in your root VC, this shouldn't cause any memory issues. This approach should be fine unless you're very particular about the animations between your view controllers.
In general, I wouldn't worry too much about memory usage until it becomes a problem. It should be fine to present view controllers on top of each other for 99% of normal use cases.
if you want to present VC B from VC A and want to dismiss VC A while Presenting you can use this Code
let parentVC = presentingViewController
dismiss(animated: true) {
let vc = self.storyboard!.instantiateViewController(withIdentifier...)
parentVC.present(vc, animated: true)`enter code here`
} `enter code here`
Apple's view controller programming guide states (emphasis added):
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.
I am attempting to implement a custom view controller presentation with a UIPresentationController subclass. When I present my view controller:
let sb = UIStoryboard.init(name:"Main", bundle:nil)
let presented = sb.instantiateViewController(withIdentifier:"PresentedTableViewController")
presented.transitioningDelegate = overlayTransitioningDelegate
presented.modalPresentationStyle = .custom
definesPresentationContext = true
present(presented, animated:true, completion:nil)
...the presentingViewController property refers to a view controller higher up the hierarchy than the one that initiated the presentation. This says to me that I have not satisfied whatever mysterious requirements that UIModalPresentationStyle.custom desires of its presenting view controller, and so it went looking elsewhere.
The thing is, I can't find these requirements documented anywhere, nor can I figure them out. I need a reference to the initiating view controller in my presentation controller. And, okay, I could sidestep the issue entirely by providing the reference myself, but I'd prefer to do things the right way rather than code around my ignorant mistakes.
Does anyone know what must be done for a view controller to qualify as a presenting view controller when using UIModalPresentationStyle.custom? Thanks!
i have this Swift project where i have a tab bar Controller Scene, and i would like to add a view controller scene to act as a "Login scene", when the user is loged i want to switch the view to the tab bar controller scene.
I have implemented facebook login, so i have a method that is called when the login is completed.
I have done this:
func loginDone(){
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("dashboardScreen") as! UITabBarController
self.presentViewController(vc, animated: true, completion: nil)
}
But that code brings me this error:
Attempt to present <UITabBarController: 0x7f87a37e8550> on <Project.LoginViewController: 0x7f87a352fca0> whose view is not in the window hierarchy!
What should i do?
Thanks!
Call it in viewDidAppear(), that way you can be sure that the login view is part of the app's view hierarchy.
You need to make sure the presenting view controller is in the app's view hierarchy. See the section 'Responding to View Events' in the UIViewController Class Reference
If you need more information on working with view controller's you can look at the View Controller Programming Guide
You should not call loginDone from init, viewDidLoad methods and others that are called before view is shown. Call it from viewDidAppear.
I need a loading screen to show while some data is being downloaded from the server. I present a view controller with modal segue without animation. But I don't know how to dismiss the loading screen view controller since dismissViewController function can only be called from inside.
I should be able to dismiss the loading screen view controller from another view controller. Any suggestions?
One option is using NSNotificationCenter. You can post custom notification and listen it in loading view for closing it.
If you are getting the data in parent view then you can close the loading view from parent view also. You can call dismissViewController from parent view using presentingViewController property of UIViewController class.
In your case from parentView you can dismiss the child view using:
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
presentingViewController
The view controller that presented this view controller. (read-only)
Declaration
Swift
var presentingViewController: UIViewController? { get }
Objective-C
#property(nonatomic, readonly) UIViewController *presentingViewController
Discussion
When you present a view controller modally (either explicitly or
implicitly) using the presentViewController:animated:completion:
method, the view controller that was presented has this property set
to the view controller that presented it. If the view controller was
not presented modally, but one of its ancestors was, this property
contains the view controller that presented the ancestor. If neither
the current view controller or any of its ancestors were presented
modally, the value in this property is nil. Import Statement
import UIKit Availability
Available in iOS 5.0 and later.
Do you need to show another VC? You could just show a subview, making it visible=true when you start the app, and on the request success or failure callbacks hide it again (depending on the networking framework you are using)
I don't know were you start the request and were you know when it is done, if the loading screen knows when the download is finished you can do one of two things, or you pass the first VC as a delegate (defining a protocol) to the second VC (loading screen), and when you know on the loading screen that the download is finished you call a method on the delegate that will dismiss the loading screen,
Or you can use NSNotificationCenter, register for some kind of events on the first VC and when the process finishes on the loading screen you notify the first VC with this method, to dismiss the loading screen.
If you start the process on the first VC, and you know when it ends also on the first VC I would not understand your question, as you would just dismiss the loading screen and it would work.
Could you give more info? If you'd like I can post some code in order to help you in one of this approaches.