Best way to deinit Initial view controller? - ios

I have a loading view controller when my app starts(is is my initial view controller).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?

Unless you're doing something very specialized, you don't need to de-init an object in Swift. It will be called automatically when the reference count goes to 0. If you really need to, you should be able to set you window's rootViewController through your AppDelegate.
However, be aware that maintenance like this is rarely necessary.

A deinitializer is called immediately before a class instance is deallocated
after you can use
if let delegate = UIApplication.shared.delegate as? AppDelegate {
let storyboard : UIStoryboard? = UIStoryboard(name: "storyboardName", bundle: nil)
let rootController = storyboard!.instantiateViewController(withIdentifier: "controllerIdentifier")
delegate.window?.rootViewController = rootController
}

Related

How to move through Views without reload them again if I return back Swift

I'll give an example of what I want so it's not so confusing:
Example:
Let's say that I have a map that adds every time that my user scrolls 3 annotations dynamically. Now I have a button under the map and when I press it I go to another viewController do what I want and get back to the viewController with the map, now I want to find all the annotations that my map had and not reload the view at all.
I used to use this function that I made to move between viewControllers:
func move(identifier: String , viewController : UIViewController) -> Void {
let mstoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc: UIViewController = mstoryboard.instantiateViewControllerWithIdentifier(identifier)
viewController.presentViewController(vc, animated: true, completion: nil)
}
I also tried this:
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("view") as? MyViewcontroller
self.presentViewController(vc!, animated: true, completion: nil)
These two when I use them the viewcontroller that appears is calling viewDidload so its like it appeared for the first time.
Another example is the tabBarViewController if you notice when you navigate through tabs nothing reloads (only function that is called is viewDidAppear )
EDIT
test file
The problem is caused by the fact that the map controller gets deallocated when navigating back to the other controller, and another one is created when you want to move again to the map screen.
What you need is to hold on onto the same controller instance, and present that one. Keeping a strong reference in the presenting controller would suffice.
class PresentingController {
// making the property lazy will result in the getter code
// being executed only when asked the first time
lazy var mapController = { () -> UIViewController in
let mstoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
return mstoryboard.instantiateViewControllerWithIdentifier("mapControllerIdentifier")
}()
func moveToMap() {
// simply use the mapController property
// the property reference will make sure the controller won't
// get deallocated, so every time you navigate to that screen
// you'll get the same controller
presentViewController(mapController, animated: true, completion: nil)
}
}
According to the same project you posted, you instantiate a new UIViewController when going from view 2 back to view 1 and that is why your viewDidLoad gets called again and your entire map view is reloaded.
In your sample project, instead of
lazy var mapController2 = { () -> UIViewController in
let mstoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
return mstoryboard.instantiateViewController(withIdentifier: "first")
}
You should just dismiss your view 2 on the button press.
#IBAction func butto(_ sender: AnyObject) {
//Your initial code
//PresentingController().moveToMap(self, flag: 1)
self.dismiss(animated: true, completion: nil)
}
When you present a new UIViewController, the older UIViewController is not removed from memory, it is just hidden behind the new UIViewController. So whenever you wish to go back to a UIViewController with the previous state maintained, all you need to do is close the new UIViewController
However, if you are doing some tasks that you performed on your second UIViewController that you wish to be reflected in your initial UIViewController, you will have to setup closures to update your initial UIViewController.

Trigger events when ViewController covered by a presented ViewController

I would like to process code when a ViewController is no longer visible due to presenting a new ViewController.
I cannot use ViewWillDisappear etc since the controller is not technically ever dismissed from the stack - you just can't see it.
What process can I use so that code runs when the controller is no longer visible (i.e. topmost) and when it becomes visible again?
EDIT:
Seems some confusion here - not sure why.
I have a viewcontroller.
I use the following code to present another controller
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navController = storyboard.instantiateViewControllerWithIdentifier("NavController") as! UINavigationController
let thisController = navController.viewControllers[0] as! MyController
self.presentViewController(navController, animated: true, completion: nil)
This controller does not trigger a viewWillDisappear on the previous controller since the previous view is not removed - just hidden.
I need to process code when this view is hidden (i.e. not visible) and, more importantly, process code when it becomes visible again.
When presenting a UIViewController if the presentation style has been set to UIModalPresentationOverCurrentContext it doesn't call the viewWillDisappear and related methods as the view never disappears or gets hidden.
A simple test to check if thats the case would be to set the NavController that you are using to have a clear background color. If you do this and present the NavController and you can still view the first UIViewController below your NavController content. Then you are using UIModalPresentationOverCurrentContext and that is why the viewDidDisappear isn't called.
Have a look at the answer referenced by Serghei Catraniuc (https://stackoverflow.com/a/30787112/4539192).
EDIT: This is in Swift 3, you can adjust your method accordingly if you're using an older version of Swift
If you won't be able to figure out why viewDidAppear and viewDidDisappear are not called, here's a workaround
protocol MyControllerDelegate {
func myControllerWillDismiss()
}
class MyController: UIViewController {
var delegate: MyControllerDelegate?
// your controller logic here
func dismiss() { // call this method when you want to dismiss your view controller
// inform delegate on dismiss that you're about to dismiss
delegate?.myControllerWillDismiss()
dismiss(animated: true, completion: nil)
}
}
class PresentingController: UIViewController, MyControllerDelegate {
func functionInWhichYouPresentMyController() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navController = storyboard.instantiateViewController(withIdentifier: "NavController") as! UINavigationController
let thisController = navController.viewControllers[0] as! MyController
thisController.delegate = self // assign self as delegate
present(navController, animated: true, completion: {
// place your code that you want executed when it disappears here
})
}
func myControllerWillDismiss() {
// this method will be called now when MyController will dismiss
// place your code that you want executed when it re-appears here
}
}
Firstly, thanks to Serghei for his time in helping work through this.
To clarify, both my potential presented controllers were set to Full Screen presentation style in the storyboard, however one was being set to Custom via a piece of pasted code dealing with the presentation. I can't find the error with the other.
However, if I force a presentation style of Full Screen as part of the presenting process then all is ok.
Hopefully my frustrating afternoon can help to save someone else's - always try to understand the implications and processes involved in pasted snippets.

Attempt to present ViewController whose view is not in the windows hierarchy

I meet a strange problem: I made 2 view controllers for wich I can switch the view with code:
var currentViewController:UIViewController=UIApplication.shared.keyWindow!.rootViewController!
func showController()
{
let ViewControllernew1 = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "viewController2")
currentViewController.present(ViewControllernew1, animated: true, completion: nil)
}
My app open correctly to the first view controller, then, when I click on the button created on a sprite kit scene, I can switch the view to my new view controller successfully (I get my second scene successfully showed) but then, I can not change anymore my view controller after this switch. If I click again on the button, I get this message:
Attempt to present on Test_Vuforia.GameViewController: 0x12f549610 whose view is not in the window hierarchy!
Do you know what is the problem ? I understand I'm in the root position so that I can not change anymore my view controller after having switched it, but how to change that ?
Thanks !
Edit:
My code is used inside a SKScene and not from a UIVewController and I get this error when I use the suffix self. : Value of type View (SKScene) has no member 'present'.
I'm creating an augmented reality game with Vuforia and I need to switch AR view with SKScene.
Issue
Current viewController is not the rootViewController from UIApplication. So you should find the current viewController which is visible and then present it from there.
Solution
Simply find the topViewController on your UIApplication Stack, and from there present your controller.
let newViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "viewController2")
UIApplication.topViewController()?.present(newViewController, animated: true, completion: nil)
This extension of UIApplication comes in handy for your case
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
References: Gist
Calling function in viewDidAppear helps in my case. Solution for Swift 3:
In your Main Controller:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
showTutorialModally()
}
func showTutorialModally() {
let tutorialViewController = TutorialViewController()
tutorialViewController.modalPresentationStyle = .overCurrentContext
present(tutorialViewController, animated: true, completion: nil)
}
In your Tutorial Controller:
view.backgroundColor = UIColor.clear
view.isOpaque = false
Use the extension below to retrieve the next available controller in the stack.
Swift 3
extension UIResponder {
func next<T: UIResponder>(_ type: T.Type) -> T? {
return next as? T ?? next?.next(type)
}
}
Swift 2.3
extension UIResponder {
func nextResponder<T: UIResponder>(_ type: T.Type) -> T? {
return nextResponder() as? T ?? nextResponder()?.nextResponder(type)
}
}
Inside your SKScene, view?.next(UIViewController.self) gives you the next available UIViewController in the hierarchy.
Add this extension to a group in your project called Categories, if this group does not exist already create it, then create a new file called UIResponder+NextOfType.swift and paste the extension.
Xcode error significance for roughly: this view is not in the Window of the view hierarchy.
What I don't think the above answer questions, but maybe you might have wondered why this would happen.
But I find that you are the reasons for this problem is likely to be in the ViewController life cycle at ViewDidLoading switch view Code execution inside.
Reason is probably that, when the ViewController implementation allco init during initialization, it will be executed asynchronously viewWillLoad - > viewDidLoad... -- -- -- -- > viewDidApper. Then may be in code execution to the viewDidLoad. The ViewController may not assign values to the Window. The rootViewController. So we directly use [self presentViewController:] will appear this error.
It is recommended that you move the code of the switch to ViewDidApper.
I hope it will help you.
Probably your rootViewController is not the current ViewController. Either you presented or pushed a new UIViewController on top of it.
The viewController'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). if you calling showController method from
viewDidLoad just call it from viewDidAppear method
Do something like:
let vc: UIViewController = (self.storyboard?.instantiateViewControllerWithIdentifier("viewController2"))!
self.presentViewController(vc, animated: true, completion: nil)
// OR
self.navigationController?.pushViewController(vc, animated: true)
Use like this
let vc = self.view?.window?.rootViewController
func showController()
{
let ViewControllernew1 = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "viewController2")
vc.present(ViewControllernew1, animated: true, completion: nil)
}
Maybe the issue is with the currentViewController.

Removing a view controller from memory when instantiating a new view controller

In my app, I am instantiating new view controllers instead of using segues because it looks better in animations as a result, my views keep running in the background. This causes large memory leaks.
My code to go back to the main screen is:
let mainStoryboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let vc : UIViewController = mainStoryboard.instantiateViewControllerWithIdentifier("MainScreen") as UIViewController
self.presentViewController(vc, animated: false, completion: nil)
This view controller is still active in the background and therefore shouldn't be instantiated again. How do I do this.
When I close my view controller using the above code, it also does not unload it, it keeps running in the background. How do I make it unload as soon as the screen disappears.
I have tried doing
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
view.removeFromSuperview()
view = nil
}
However this does not work properly. How do I properly destroy a view controller from memory when exiting a view controller in this manner.
You need only to use:
EDIT Swift 4.2
self.dismiss(animated:true, completion: nil)
The rest of work is doing by ARC
To help you during your debug you can add also this code:
if let app = UIApplication.shared.delegate as? AppDelegate, let window = app.window {
if let viewControllers = window.rootViewController?.children {
for viewController in viewControllers {
print(viewController.debugDescription)
}
}
}
An important reason for this problem is related to the memory management!
if you have 'strong reference' or 'delegate' or 'closure' or other things like this, and you didn't managed these objects, your view controller has strong reference and never be closed.
you should get 'deinit' callback in view controller after than viewDidDisappear called. if 'deinit' not called so your view controller still is alive and it has strong reference.

How to redraw my view in SWIFT?

On my iPad app, I have a UIViewController with a button that open a modalView.
#IBAction func showPostCommentViewController(sender: AnyObject){
let modalView = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("PostCommentViewController") as! PostCommentViewController
modalView.modalTransitionStyle = UIModalTransitionStyle.CoverVertical
modalView.modalPresentationStyle = UIModalPresentationStyle.FormSheet
modalView.delegate=self
self.presentViewController(modalView, animated: true, completion: nil)
}
When I close the modalView with dismissViewControllerAnimated, I would like "refresh" my view controller (because I added new content). But as the modal view is a "formsheet" style, viewDidAppear or viewWillAppear aren't called.
I tried to use setNeedsDisplay, but it doesn't work.
I don't know how to do.
This would be a perfect use case for the delegate pattern.
1) define a protocol within PostCommentViewController.
protocol PostCommentVCInformationDelegate {
func hasDismissedPostCommentViewController(controller:PostCommentViewController)
}
2) Set a delegate variable within PostCommentViewController
var delegate: PostCommentVCInformationDelegate?
3) When you dismiss PostCommentViewController, you will call delegate?.hasDismissedPostCommentViewController(self)
This will send information back to the presenting VC.
4) Now we have our presenting View Controller conform to this protocol.
class ViewController: UIViewController, PostCommentVCInformationDelegate
5) When presenting the modal View:
modalView.delegate = self
6) Finally, we implement:
func hasDismissedPostCommentViewController(controller: PostCommentViewController) {
//Update
}

Resources