I have an app where UIViewControllers are pushed onto a UINavigationController using pushViewController. Once in a while, seemingly randomly, the pushed view controller loads (I know this because viewDidLoad is called), but it doesn't actually appear (viewDidAppear is not called). The app enters a state where the new view controller isn't visible, but presumably is on the screen because I am unable to interact with the view controller behind it. In other words, the UI is completely non responsive, but the app continues to run (background and main thread events continue to fire, such as animations and network calls).
Anyone have any idea what could cause a situation like this? Or any ideas about how to go about debugging the cause?
Related
I'm facing a very weird bug. I have a button and I'm pushing a view controller normally, using segues. However, when I push the view controller the whole app layout messes up: it sometimes works, sometimes glitches such as leaving me with a blank view with nothing but the tabbar, or previous view controller still being seen but transformed to somewhere else in the screen etc. If I manage to go back, it always works fine after the first try. Here are some observations:
Problem is specific to device (iPhone X). Simulator (even the same model and same OS version) works perfectly.
Switching to modal doesn't matter. The "glitchy look" animates a bit different, but the problem is still there.
Turning off animation in transition doesn't matter. It presents me a glitchy screen instantly, just without animation.
Pushing/presenting the view controller from code (as opposed to storyboard segue) doesn't matter. Exactly same.
Giving a delay (e.g. half second) after tapping the button before presenting the view controller doesn't change anything. Just wanted to try this to see if some race condition is present on tap, for whatever reason.
The problem is specific to one view controller. Presenting anything else at the same segue/state doesn't cause any problem.
The problematic view controller doesn't have anything special at all: It's actually just a wrapper with three child view controllers, something I commonly do:
When I'm trying to present the problematic controller, I'm always getting this weird log: [Render] CoreAnimation: failed to allocate 1223558576 bytes
My application is definitely not out of memory. It's using ~50MB on iPhone X at the time of problem. It's a media app and can allocate ~500MB with no issues or crashes when shooting/filtering video etc.
The problem occurs if a specific embedded view controller (the second one of three) is present. For example, if I remove the embed segue to that, it seems to run perfectly.
That embedded view controller is a simple UIViewController subclass that just has a table view and some cells.
What might be going on?
Basic question:
Is there a reliably way to trigger showing modal UIViewControllers at any point in the app's lifetime (including from different threads)?
My current approach is to call presentViewController on the showing ViewController (found through window.rootViewController + hierarchy traversing but that's unimportant). This generally works, but is sometimes ignored due to things like a navigation action/animation taking place.
E.g. a background thread signals for a popup to be shown, and presentViewController is called on a ViewController in the process of being dismissed.
I've tried a few work arounds such as repeating the signal if the ViewController isn't shown (which led to some instances of it being show multiple times), but it's ended up being a game of whackamole.
An ideal solution would also allow navigation to take place underneath the popup, but the primary issue right now is just reliability.
edit
To be clear, I’m a seasoned developer. The threading is being handled properly, the instance and type management is working. My problem is trying to manage all
the corner cases, not the basics of how to do it.
If you need mechanism for thread safe showing different VCs in multithread environment, you can make some object which is responsible for presenting/dismissing controllers. And make some queue on presenting/dismissing. So when your signal occurs, your operation on presenting will be next after dismissing current VC in queue
Summary of question
UINavigationControllerDelegate:didShowViewContoller makes it possible to get notified whenever any view controller has been displayed (as opposed to being loaded), provided its within the context of a navigation stack.
I want to know if such observation is possible for all view controllers if there isn't a navigation stack.
More background
I have an app where view controllers can suddenly appear based upon timers and local notifications firing, thus their appearance is effectively random.
If one VC triggers and gets displayed at the same time as another was in the process of getting displayed then there can be an issue (if you're experienced with iOS you'll be aware if one VC pushes another from within its viewDidLoad, rather than its viewDidAppear you will get an "attempting to present X on Y whose view is not in the window hierarchy" error)
How I solve this is I have a list of VCs to display and they get displayed by a view controller co-ordinator which implements UINavigationControllerDelegate's didShowViewContoller and doesn't display the new VC until didShowViewController has been invoked.
This works perfectly.
But now my problem is I want to do a similar thing for an app that isn't using a navigation controller, and thus I can't use UINavigationControllerDelegate:didShowViewController to observe globally when a view controller has been displayed. Does anybody know of another elegant mechanism for doing so?
I changed navigation in my application from using UITabBarController to u UINavigationController. I.e. former solution (1st version) was based only on the TabBarController - 4 ViewControllers (one simple TableView, one simple custom view and one MapView with many overlays). The second version is based only on the UINavigationController.
In case of TabBarController it was clear and simple, everything worked fine, especially MapView. I mean: the MapView was loaded once (with a significant number of overlays) and when I went to another view and back to the MapView the MapView was still there with its overlays already loaded and displayed (simple check: MapView`s viewDidLoad was called just once per app run, I had some debug messages there).
Now I changed navigation logic to the UINavigationController. Everything works fine for the first look - but: the viewDidLoad (for each view) is called everytime I navigate to the view. It is annoying especially in the case of the MapView - the loading of overlays is performed everytime, it takes some time and it causes app crash in some cases.
OK, my questions:
Is it some kind of "common" behavior of NavigationController?
Can I change this behavior so viewDidLoad will be called just once?
And more - How can I influence the "display sequence" of some view?
I understand the logic is probably more complicated but I appreciate any answer or hint ;)
Some related circumstances:
TabBar and Navigation controllers are not combined.
I use storyboards, segues are designed in the UIB, no manual calling like perfomSegue or prepareForSegue in my code. One button triggers segue to MapView.
I use push segues.
I also tried to use modal segues but without any change of that behavior.
any of viewDidUnload is never called during segues among the views.
No memory warning received.
No memory leaks measured both on simulator and iPhone 4.
I tried to build a very simple temporary project / app that is concerned just about the Nav. Controller and other views without ANY coding, just storyboard. It was the same behavior.
There was an issue that causes app crash when I fast and periodically tapped to navigation button and back button between one view and the MapView. In most cases the app crashed when I tapped the back button on the MapView before it was fully displayed (i.e. its overlays). It was fixed when I added a 1 sec. delay method call in the viewDidDisappeared in the MapView. It is not a fair fix, I know ;)
A UITabBarController and UINavigationController are based on fundamentally different paradigms.
The UITabBarController is intended for the UIViewController on each tab to exist independently of each other and for the user to choose which they want to view. viewDidLoad only gets called once for each UIViewController because it is intended that each tab still exists in memory even as the user switches to a different tab.
The UINavigationController is a stack of UIViewControllers where each is related to the one above and beneath itself. The top UIViewController in the stack is always the one that is visible to the user. When a UIViewController is pushed to the stack, its viewDidLoad gets called because it is being loaded into memory. When the top UIViewControllergets poped off the stack, it is unloaded from memory, and viewDidUnload gets called on the way out (viewDidUnload is deprecated in iOS6 and won't get called, but the controller will still get dumped from memory). This is why viewDidLoad gets called every time that the user pushes a particular UIViewController onto the UINavigationController stack.
How do I make a curled-up view update live as the user interacts with view being presented with presentModalViewController: under it?
The behaviour I want:
User taps a view settings button.
User taps controls in the view settings screen. Rather than dismissing view settings, the view automatically updates in the background.
User taps something to dismiss view settings.
Imagine if in Maps tapping Map, Satellite, Hybrid didn't uncurl automatically but just updated the display.
I'm able to get notification that something's changed from the settings controller back to the main view controller. The main view controller can update and signal that it should redraw parts of itself, but actually updating the screen is intermittent. It will work 5 times in a row, then not work a couple times, then work another 5 times in a row. Dismissing the modal view always catches up the view underneath, however, so I assume the rendered image of my view is sometimes being cached or not being redrawn despite my request. But I can't think of a way to verify this.
This happens on both the device and the simulator.
While there might be multiple root causes of this behavior, here's a common issue I've seen that causes 'delayed' or 'intermittent' updates to UIKit views.
If the statements that update your presenting view (or the presented view) are running in a dispatch queue other than the main queue, UIKit may produce inconsistent results and timing in the actual UI update. You can usually tell by putting a breakpoint on the statements that update the UI and examining the name of the 'queue' displayed in Xcode's left-side debugger view. It should be 'com.apple.main-thread'. If it's not, that could be your issue. This can happen in quite a few delegate methods such as the network APIs.
Try wrapping your UI updates in:
dispatch_async(dispatch_get_main_queue(), ^() { ... }); and see if that helps! You should only do this when needed and take care to use block-safe techniques as always.
I tested this in a brand new Universal app for iOS 7.0.3 using the iPad simulator with a view controller presented using the partial curl transition. I was able to replicate the original issue, including the intermittent update and the 'snap' update when dismissing the presented view by using a background queue in the code I provided above. Once I switched to the main queue, everything worked A-OK.
Let me know if this helps or if there was some other issue :)