I have a loading view controller when my app starts, when an animation in this view controller finished I want it to show another view controller and dismiss the view controller with the animation.
The loading view controller is the initial view controller,
I have this code when UIStoryboard.mflMainTabBarViewController(). returns the view controller that I want to present
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
let animationID = anim.value(forKey: "animationID")
if animationID as! NSString == "transform" {
self.present(UIStoryboard.mflMainTabBarViewController(), animated: true, completion: {
_ = self.popoverPresentationController
})
}
}`
But when deinit is never called
deinit {
print("deinit")
}
What is the best way to deinit the first view controller, and making the presenting view controller the root view controller?
When you are using weak reference cycle at that time deinit method is calling. In strong reference cycle deinit not calling. So create weak reference cycle for calling the method.
Also refer this link.
try this.
Use the vc's parent to present.
And dismiss the vc itself.ðŸ¤
self.presentingViewController?.present(UIStoryboard.mflMainTabBarViewController(), animated: true, completion: {
_ = self.popoverPresentationController
})
self.dismiss(animated: false, completion: nil)
or
self.navigationController?.present(UIStoryboard.mflMainTabBarViewController(), animated: true, completion: {
_ = self.popoverPresentationController
})
self.navigationController?.popViewController(animated: false)
If you are 100% sure that self will never be nil, then just use
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
let animationID = anim.value(forKey: "animationID")
if animationID as! NSString == "transform" {
self.present(UIStoryboard.mflMainTabBarViewController(), animated: true, completion: { [unowned self] in
_ = self.popoverPresentationController
})
}
}`
weak and unowned are the same. Except one thing. Unlike weak we’ve seen, unowned does not automatically convert self as optional within the closure block.
Related
I have two view controller that opens modally. When the first VC closed then the second should be opened. But when I close the first one, the second one is not displayed at all.
what is the problem?
My code is:
self.dismiss(animated: true) {
let flowVC = LanguageFlowViewController()
self.present(flowVC, animated: true)
}
You need reference of view controller from where you to present first view controller.
For example, you have view controller name as X, from there your first view controller A present. So you need reference of X to present B, because A will not be available in memory.
So when you try to present second view controller using self, it will do nothing.
So, for solution assign reference of X view controller to A. In class A, declare:
var refX: X?
While present A from X, set self to refX. Like:
var aVC = A() // This is temp, you need to instantiate your view controller here.
aVC.refX = self
self.present(aVC, animated: true, completion: nil)
Now, inside view controller A, when dismiss:
var bVC = B() // This is temp, you need to instantiate your view controller here.
self.dismiss(animated: true) {
if self.refX != nil {
self.refX?.present(bVC, animated: true, completion: nil)
}
}
I hope this will help you.
If you want to open modally ThirdViewController after Dismiss SecondViewController then you have to create protocol for it.
Like we have three UIViewController(FirstViewController,SecondViewController and
ThirdViewController)
Step 1: We need to create a protocol in SecondViewController as given below code.
protocol DismissedViewProtocal {
func dismissView()
}
class SecondViewController: UIViewController {
var delegate: DismissedViewProtocal?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func dismissSecondViewAction(_sender : AnyObject) {
dismiss(animated: true) {
self.delegate?.dismissView()
}
}
Step 2: You need to add protocol in FirstViewController as given below
class FirstViewController: UIViewController, DismissedViewProtocal {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func anAction(_sender : AnyObject){
let flowVC = self.storyboard?.instantiateViewController(withIdentifier:"SecondViewController") as? SecondViewController
secondVC?.delegate = self
self.present(secondVC!, animated: true) {
}
}
func dismissView() {
let thirdVC = self.storyboard?.instantiateViewController(withIdentifier:"ThirdViewController")
self.present(thirdVC!, animated: true) {
}
}
}
Your are dismissing the current view controller by calling self.dismiss().
Therefore it is impossible for it to present anything anymore, since it is removed from the view hierarchy. As others have mentioned, try using the self.presentingViewController or self.navigationController (if it is on a navigationController) to present your new view.
However, if you need maximum flexibility create a delegate protocol. Create a protocol with a function presentForChild(viewController: UIViewController). Before your previous view presents the view from which the code in your question belongs to, give it a reference of the protocol.
Example:
protocol ChildPresentDelegate: class {
func presentForChild(vc: UIViewController)
}
class FirstController: UIViewController, ChildPresentDelegate {
func presentForChild(vc: UIViewController) {
present(vc, animated: true, completion: nil)
}
/**
other code
*/
func showControllerAsWasShownInTheQuestion() {
let controller = SecondController()
controller.delegate = self
present(controller, animated: true, completion: nil)
}
}
class SecondController: UIViewController {
weak var delegate: ChildPresentDelegate?
func dismissMySelf() {
self.dismiss(animated: true) {
delegate?.presentForChild(vc: LanguageFlowViewController())
}
}
}
I have created a CustomAlertView.
protocol CustomAlertViewControllerDelegate{
func retryToFetchData()
}
class CustomAlertViewController: UIViewController {
#IBOutlet weak var alertView: UIView!
var delegate: CustomAlertViewControllerDelegate!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.alertView.alpha = 0
self.alertView.frame.origin.y = self.alertView.frame.origin.y + 50
UIView.animate(withDuration: 0.4) {
self.alertView.alpha = 1.0
self.alertView.frame.origin.y = self.alertView.frame.origin.y - 50
}
}
#IBAction func retryButton(_ sender: UIButton) {
delegate?.retryToFetchData()
self.dismiss(animated: true, completion: nil)
}
}
I have created a static function to show that view. The view is just a UIViewController that will have a child View which will act as a popUP, with a transparent background.
func showErrorBox(view: UIViewController, message: String, delegate: CustomAlertViewControllerDelegate){
let customAlert = view.storyboard?.instantiateViewController(withIdentifier: "customAlertViewController") as! CustomAlertViewController
customAlert.providesPresentationContextTransitionStyle = true
customAlert.definesPresentationContext = true
customAlert.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
customAlert.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
customAlert.delegate = delegate
view.present(customAlert, animated: true, completion: nil)
}
Now, I am calling this VC anywhere I needed to show a popUP with showErrorBox(view: self, message: message, delegate: self) now my issue is I need to show this popup in a ViewController which is inside of a TabBarController, when I change the view between the UITabBar, and try pressing the reload button, the app throws an error,
presentViewController does not work: view is not in the window
hierarchy
Edit:
There is a Retry button on the ErrorBox(popUp). The error happens only when the error box is loaded and I changed the view to different tab and then hit the reload button. In normal scenario like, hitting the reload button when I am in the same page works fine.
I am not sure of issue, but it has something to do with when I change the view between tabs when the error box is present.
try self.present(customAlert, animated: true, completion: nil) instead view.present(customAlert, animated: true, completion: nil). If I understand your problem, should works
Solution,
The problem was, when I changed the views in TabController the reference to the customAlertView was removed from the window hierarchy. So, As soon as I leave the TabView which was loading the CustomAlertView need to be removed.
So to solve the issue, I have added following code in CustomAlertViewController
override func viewWillDisappear(_ animated: Bool) {
super. viewWillDisappear(animated)
self.dismiss(animated: true, completion: nil)
}
Try to getting the TopMost ViewController and presenting CustomAlertViewController from it instead of "self".
refer this link to :
GetTopMostViewController
As SplitViewController loads, I am showing a Login Screen. On successful login, I need to go back to parent view controller. Somehow dismissal is not working for me. Here is the code:
ParentViewController:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if !appDelegate.loggedIn {
self.performSegueWithIdentifier("loginScreen", sender: self)
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
Child ViewController:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.loggedIn = true
self.dismissViewControllerAnimated(true, completion: nil)
The dismissal part never works. It just hangs on Login Screen.
Try one of the following:
1) remove self. keep only dismissViewControllerAnimated(true, completion: nil)
or remove self. and make it:
2) presentingViewController.dismissViewControllerAnimated(true, completion: nil)
or remove self. and try:
3) presentedViewController.dismissViewControllerAnimated(true, completion: nil)
Try this in your parent view controller:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if !appDelegate.loggedIn {
let loginVC: UIViewController = self.storyboard!.instantiateViewControllerWithIdentifier("LoginViewController") as UIViewController
loginVC = UIModalTransitionStyle.CoverVertical
self.parentViewController?.presentViewController(loginVC, animated: true, completion: nil)
}
}
You're instantiating the new view controller by its own name rather than by the segue name.
I am presenting two view controller like so
self.presentViewController(choosePlace, animated: true, completion: nil)
self.presentViewController(shareController, animated: true, completion: nil)
and I want to dismiss both of them like this:
println("parent \(self.parentViewController)")
self.dismissViewControllerAnimated(true, completion: {
self.parentViewController?.dismissViewControllerAnimated(true, completion: nil)
})
self.parentViewController is always nil though. How can I dismiss two at the same time?
You can:
self.dismissViewControllerAnimated(true, completion: {
self.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
})
So, make sure that you are presenting your view controllers in order:
parentViewController -> choosePlace -> shareController
(arrows indicate "self.presentViewController")
Swift 4
There where some issue I faced while trying Michael Voline's answer.
As the comment on the answer it did not work for me. it was because of the presentingViewController was nil.
So we need to set in a different property say presentingController but this will not work if you are setting it on the viewDidLoad
private var presentingController: UIViewController?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentingController = presentingViewController
}
then
dismiss(animated: false, completion: {
self.presentingController?.dismiss(animated: false)
})
I made the animation false so that the user will not see the UIof presenting view controller in the split seconds of dismissing.
You can reference the presenting view controller during the function 'viewWillDisappear' of your view controller (i.e ShareController dismisses choosePlace just before it leaves scope).
//place the below in shareController
override func viewWillDisappear(_ animated: Bool) {
self.presentingViewController?.dismiss(animated: false, completion: nil)
}
If Michael Voline's solution doesn't work , try the following code.
let customViewController = self.presentingViewController as? CustomViewController
self.dismiss(animated: true) {
customViewController?.dismiss(animated: true, completion: nil)
}
I have a Parent UIViewController, which opens a child UIViewController:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("myChildView") as! UIViewController
self.presentViewController(vc, animated: true, completion: nil)
I press a Button in the ChildView which should close the the ChildView and call a method in the Parent View:
self.dismissViewControllerAnimated(true, completion: nil)
CALL PARENTS METHOD ??????
How to do that ?
I found a good answer (Link to good answer), but I am not sure if this is the best practice with UIViewControllers. Can someone help ?
One easy way to achieve this you can use NSNotificationCenter for that.
In your ParentViewController add this into viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: Selector(("refresh:")), name:NSNotification.Name(rawValue: "refresh"), object: nil)
}
After that add this function in ParentViewController which will get called when you dismiss your ChildViewController:
func refreshList(notification: NSNotification){
print("parent method is called")
}
and into your ChildViewController add this code where you dismissing your child view:
#IBAction func btnPressed(sender: AnyObject) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "refresh"), object: nil)
self.dismiss(animated: true, completion: nil)
}
Now when you dismiss child view refreshList method will be call.
Add a weak property to the child view controller that should contain a reference to the parent view controller
class ChildViewController: UIViewController {
weak var parentViewController: UIViewController?
....
}
Then in your code (I'm assuming it's inside your parent view controller),
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("myChildView") as! ChildViewController
vc.parentViewController = self
self.presentViewController(vc, animated: true, completion: nil)
And, as vomako said, call the parent's method before dismissing your child view controller.
parentViewController.someMethod()
self.dismissViewControllerAnimated(true, completion: nil)
Or, you can also call the method in the completion paramter of dismissViewControllerAnimated, where it will be run after you child view controller dismisses:
self.dismissViewControllerAnimated(true) {
parentViewController.someMethod()
}
Something that I've noticed in your question: Do not call a method (the parent method in your case) after dismissing the view controller. Dismissing the view controller will cause it to be deallocated. Later commands may not be executed.
The link that you've included in your question points to a good answer. In your case, I would use delegation. Call the delegation method in the parent view controller before you dismiss the child view controller.
Here is an excellent tutorial.
protocol SampleProtocol
{
func someMethod()
}
class parentViewController: SampleProtocol
{
// Conforming to SampleProtocol
func someMethod() {
}
}
class ChildViewController
{
var delegate:SampleProtocol?
}
self.dismissViewControllerAnimated(true, completion: nil)
delegate?.someMethod()