I am facing a serious issue. I have a framework named "abc.framework" and it contains a function in which you can pass a viewController object.
//this is the method in my framework
func launchView(view:UIViewController){
}
//this is what needs to be written on App side
myFrameworkFile.launchView(view: self)
The above method is used to present the Framework's viewcontroller i.e abc.framework viewController on top of the the viewController object passed. The launch method contains the following code :
func launchView(view:UIViewController)
{
var rootController: UIViewController?
rootController = view
let storyboard = UIStoryboard(name: "abcFrameworkBundle.bundle/abcUIMain", bundle: nil)
let VC1 = storyboard.instantiateViewController(withIdentifier: "selectedviewcontroller") as! SelectedViewController
rootController?.present(VC1, animated: true, completion: nil)
}
From the above method the viewController the SelectedViewController viewDidLoad method is getting called however it is not getting presented and I am getting the below warning
Warning: Attempt to present on
whose view is not in the window hierarchy!
Has anyone faced the same issue?
I guess you call this method myFrameworkFile.launchView(view: self) in viewDidLoad or earlier. Try to move it inside viewDidAppear
Related
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.
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.
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.
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.
I have a problem, where I have two view controllers A and B. View controller B has a map, with a route draw on it. I can move back and forwards between the two view controllers at the moment, but the B view controller is reset every time it loads. I think this is because I am using segues and it is creating a new instance of a View controller every time.
I have tried using the following code to solve this issue, but it is still not working. The views load correctly, but view controller B is still being reset
#IBAction func mapButton(sender: AnyObject){
let storyboard = UIStoryboard(name: "MainStoryboard", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("SecondViewController") as! UIViewController
self.presentViewController(vc, animated: true, completion: nil)
}
What am I doing wrong and how can I fix it? I want view controller B to stay in the memory along with the map and the route, so when a user returns he doesn't have to enter all of the information again.
You should create a variable in your class of type UIViewController and change your code to the following:
#IBAction func mapButton(sender: AnyObject){
if yourVariable == nil {
let storyboard = UIStoryboard(name: "MainStoryboard", bundle: nil)
yourVariable = storyboard.instantiateViewControllerWithIdentifier("SecondViewController") as! UIViewController
}
self.presentViewController(yourVariable, animated: true, completion: nil)
}
That way you create the viewController one time, save it and if you want to open it again present the previously created one.