I'm experiencing something really weird :
Create an extremely basic single view project, and add a second view controller to the storyboard, along with a modal segue from the first to the second. Initiate the segue from the view controller and trigger it programmatically with performSegueWithIdentifier:.
In the viewDidLoad of the modally presented view controller, add this log :
NSLog(#"%#", self.presentingViewController);
Now run the app on iOS 7, you should get a log like this one :
<ViewController: 0x7fa8e9530080>
Which is just the reference of the initial view controller of the app, which presented the modal view controller.
Now run the exact same thing on iOS 8, and you will get :
(null)
What's going on here ? Is it a known issue ? Of course I'd expect the exact same behavior on both systems.
Thanks ... Formalizsed as answer.
viewDidLoad should really be used for initialization, At this stage, there is not guarantee that the receiver's controllers view hierarchy has been placed in the navigation tree. If that is your intent, you should override viewWillAppear or viewDidAppear. Whilst it works in earlier versions, the docs clearly state that it should be used for additional initialization. It certainly sounds as though in iOS 8, the receiver's initialization is being performed earlier
Related
I have a modal view on top of a navigation controller.
I wish to destroy the whole stack and re-create a new one (reload).
However, when assigning the new one to window.rootViewController, warnings appears during runtime and view controllers are not deallocated.
To make things more complicated, I am auto navigating to the modal view controller 'automatically' upon reload, and that < iOS 12 and iOS 13 behaves differently.
I have attached a reprex that demonstrates the issues that will appear. E.g. if you run it as is, on iOS 13, you will see the counter jump from 1 to 3, while on iOS 12, it jumps from 1 to 2. Both are leaking memory (some or all view controllers are not being unloaded).
The main issue is that regardless of how you replace the view hierarchy, your presented VC will dismiss which will send a viewWillAppear message to your "pushedVC" ... at which point "pushedVC" will immediately load and re-present "triggerVC"
What you probably want to do is:
Leave jumpToModalVC equal to false, until you want to use it
On button tap in presented "triggerVC", dismiss the modal (self)
on completion of the dismiss, set jumpToModalVC to true and rebuild / reset your hierarchy
Note:
Chained calls to segues from each VC's viewWillAppear almost always leads to:
Unbalanced calls to begin/end appearance transitions
To avoid that, it's best to trigger the segues from viewDidAppear
If you want, you can add me as a "Collaborator" on your GitHub repo (my GitHub user ID is DonMag), and I can push the changes I made as a new branch.
I have 3 view controllers that I'm navigating between. When I open my app, I start at Controller1, which I can then use to navigate to Controller2 or Controller3. I can navigate to each of them fine individually, however, after I go to Controller3, return to Controller1, then try to navigate to Controller2, I get an EXC_BAD_ACCESS with code = 1. There is no exception or error message given at all, it just takes me to my AppDelegate file and gives me that error code.
I don't know what the issue is, but something that seems relevant is that I'm setting Controller3 as the navigation controller's delegate. I have a fourth navigation controller that also is set as a delegate as well, and causes the same behavior when I from 1->4->1->2, just like with 1->3->1->2. I have no issue going from 1->4->1->3 or 1->3->1->4, only when 2 is involved. I'm not sure if the issue is the delegates, and the fact that 2 isn't being set as one. Once again, I can navigate to it fine by itself, but not after navigating to one of the other 2 sub-view controllers.
If you set Controller3 or Controller4 as the navigation controller delegate then you need to clear it in the viewWillDisappear function of those classes otherwise you will end up with an invalid reference and that is what is causing your crash
I ran into this same kind of crash, so I will share what the issue was for me:
I had created a subclass of UIWebView.
In my storyboard, I dragged a UIWebView onto the canvas and changed it's class to be my subclass.
That was all working. The app had already passed through our QA team, was "accepted" by the business owner, and we were ready to push it to the app store.
Then I was told, "You can't use UIWebView. You must use WKWebView."
Fine, I changed my subclass to inherit from WKWebView, tweaked my internal class logic, and....splat. Trying to segue to that view controller would crash just as noted by the OP.
The problem was my storyboard: Because it was trying to instantiate my subclass, it was basically attempting to create a WKWebView which is NOT necessarily supported in Interface Builder (my friend says it might be ok in later versions, but I didn't verify).
TL;DR
The moral of the story is that if you have a subclassed object on your story board whose ancestor can't be dragged from the toolbox, then you will probably crash when you segue.
Wondering if anyone else has encountered this issue recently...
For one of my view controllers , only on iOS 8, after calling presentViewController:animated:completion:, the presented view controller has self.presentingController as nil. It is fine on iOS7, and also does not happen on another view controller.
The documentation says that it should be set, as long as the presented view controller was presented modally. Given it works in iOS 7, could this be an iOS 8 bug?
I've been able to get around it using a view container containment approach, but it would be good if someone has seen this before and knows the root cause that triggered this behaviour.
thanks
Had a similar issue with iOS 8, where presentingController is nil when checking the value in viewDidLoad.
When viewDidLoad is called, there is no guarantee that the view controller hierarchy is loaded in the navigation tree. Moving the logic to a later stage (for example: viewWillAppear) should resolve that issue as presentingController should be loaded by then.
My app has a simple organization, which I've configured in an Interface Builder storyboard (not in code). There is a Navigation View Controller, which has its Root View Controller set to my Main View Controller. My Main View contains a table, where cells segue to a Detail View Controller.
When I suspend the application while looking at the Detail View and then resume it, I'm returned to the Main View, rather than the Detail view. Why might this be?
Details:
I have set Restoration IDs in Interface Builder for the Navigation View Controller, the Main View Controller and the Detail View Controller. I've also tried adding a Restoration ID to the Table View and making the Main View Controller implement UIDataSourceModelAssociation.
My app is returning YES from shouldRestoreApplicationState and both the Main View and the Detail View have encode/decodeRestorableStateWithCoder methods.
I'm testing suspend/resume using the simulator: I run the app, navigate to the Detail View, hit the home button, and then click the stop button in XCode. To resume, I'm running the app again from XCode.
I see the following calls on suspend:
AppDelegate shouldSaveApplicationState
MainViewController encodeRestorableStateWithCoder
DetailViewController encodeRestorableStateWithCoder
And on resume:
AppDelegate shouldRestoreApplicationState
AppDelegate viewControllerWithRestorationIdentifierPath Navigation
AppDelegate viewControllerWithRestorationIdentifierPath Navigation/MainView
MainViewController viewDidLoad
AppDelegate viewControllerWithRestorationIdentifierPath Navigation/DetailView
MainViewController decodeRestorableStateWithCoder
In addition to the wrong view being restored, there's something else odd: Why is the Restoration Identifier Path for the Detail View "Navigation/DetailView" and not "Navigation/MainView/DetailView"? There is no direct relationship between the Navigation View Controller and the Detail View Controller. Their only connection in Interface Builder is via the segue from the Main View.
Have I misconfigured something?
I have tried assigning a Restoration Class to the Detail View. When that restoration code is invoked, it fails because the UIStateRestorationViewControllerStoryboardKey is not set in the coder.
Here's a toy version of my project which replicates the problem: https://github.com/WanderingStar/RestorationTest
I'm trying this with XCode Version 5.0 (5A1413) and iOS Simulator Version 7.0 (463.9.4), in case those are relevant.
The answer turned out to be simple: I was not calling
[super encodeRestorableStateWithCoder:coder];
in the encodeRestorableStateWithCoder:coder method in my View Controllers (and doing the same in decode...) which is what sets the storyboard in the coder.
This tutorial helped me step through each step of the process, and find out where I'd gone wrong:
http://useyourloaf.com/blog/2013/05/21/state-preservation-and-restoration.html
Also, it turns out that "Navigation/DetailView" is what's expected. The Navigation View Controller restores all of the views in its stack and then puts them back into the stack, rather than each view restoring the later views in the stack.
In the iOS App Programming Guide, section "State Preservation and Restoration" there is a convenient checklist for what you have to do to make restoration work.
After looking at your code it seems that you forgot step 3, Assign Restoration Classes. Your classes do not have these properties, and you did not implement viewControllerWithRestorationIdentifierPath in the app delegate.
Assign restoration classes to the appropriate view controllers. (If you do not do this, your app delegate is asked to provide the corresponding view controller at restore time.) See “Restoring Your View Controllers at Launch Time.”
I took a look at your sample and the applicationWillFinishLaunching is missing [self.window makeKeyAndVisible] which is a requirement for state restoration. This will make the split controller immediately collapse and then it will be restored correctly.
There is an issue that if it was preserved in landscape, i.e. separated split view , and then launched in portrait then the path will not be correct. In this case at launch it will first collapse to match the current screen, then it begin restore and first separate, then after restore has finished it will collapse again to match the current screen. During this time you need to implement viewControllerWithRestorationIdentifierPath and use the last string in the path to identify the controller and return it after having captured it from what the storyboard created initially in will finish launching. Then you can clear those properties in didFinish.
I have to support down to iOS 4.3.
My app outputs in the console :
Using two-stage rotation animation. To use the smoother single-stage animation, this application must remove two-stage method implementations.
As far as I know I'm not using two-staged rotation. I just have this method in my view controllers :
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
What else should I check in order to fix that?
Edit:
More precisions: my app uses a UITabbarController subclass. When the app starts, it checks if a user is logged in and then initiates the controllers of the tabbar controller if it's the case. If there's no user logged in, a modal view is presented (from the tabbar controller) the prompt the user to login and the controllers of the tabbar controller aren't initialized (yet). The "two-staged rotation" error is shown only at that moment and the rotation doesn't work.
So to summarize, the problem happens in that situation:
The rootViewController of the main window is the tabbar controller
The tabbar controller is empty (there are no view controller in the tabs and there's no tab)
A view controller is modally presented from the tabbar controller
OK I found a solution.
It seems like the presented modal view won't rotate until the viewControllers property of the UITabBarController is initialized. Since the concerned modal view is actually for login, I don't want to display anything behind it until the user is logged in because the views intended to be hold by the tabbar controller depend on the fact that a user is logged in.
So now just before presenting the modal view, I initialize the tabbar controller with a single, empty UIViewController and I remove it when the modal view is dismissed (i.e. a user logged in).
Perhaps it seems like a hack but it works well. And even if I don't understand why, it doesn't seem completely illogical that the tabbar controller doesn't behave like we want until it is fully initialized.
If someone has a better solution or explanation, feel free to comment :)
There aren't many posts regarding this error, so I'll admit my own shortcomings for the benefit of the next person so focused on the trees they might miss the forest. I found a missing
[super viewWillAppear:animated];
call inside my sub.