Like many developers, I have an app that uses OpenGL via a UIView
subclass whose layerClass:
method returns [CAEAGLLayer class]
.
Note I am not using GLKit
or GLKView
or GLKViewController
When I click Home to put the app into the background, after applicationDidEnterBackground
, iOS calls my view's layoutSubviews
twice, with portrait and landscape sizes, trying to generate an "app snapshot" as explained here (see "prepare for the app snapshot"):
How can this possibly work?
There seems to be a direct contradiction here with the very clear advice on this page (see "Background Apps May Not Execute Commands on the Graphics Hardware"):
that we must not draw anything with OpenGL after applicationDidEnterBackground
If we don't draw, we cannot generate the snapshots. We must violate one rule or the other.
But we also want good snapshots in both orientations, so that when the user double-clicks home and goes to the App Switcher, they see reasonable snapshot images.
Even if I temporarily change my code to fully implement the layoutSubviews
after applicationDidEnterBackground
by creating an OpenGL surface and drawing (which, contrary to the Apple dox, does not crash), and then I double-click home and look at the snapshot in different orientations, only the snapshot for the orientation I was in before is correct. The other one is a super-ugly nasty re-scaling of the other snapshot. Apple seems to be going through the motions of taking snapshots, but not actually taking them.
I am seeing this behavior on iOS 9.3.2 on an iPad Mini. The behavior doesn't show on most/all iPhone devices since they don't support a landscape App Switcher.
UPDATE: the problem also happens, and happens much worse, when using the new iOS 9 "Slide Over" multitasking feature and switching the same app between being a normal fullscreen app vs. being an app slid over another app. iOS only seems to capture a snapshot of the last app size, so after using the app at 640px wide and then trying to use the App Switcher to get to the app fullscreen, we see a grotesque pixely out-of-proportion snapshot in the App Switcher and also during the first second of launch. There has got to be some way to fix this!
UPDATE 2: I have seen a few iOS apps, which I know to be OpenGL-only apps, where if you use them in portrait, then go back Home and rotate to landscape, then double-click Home, you see the portrait launch image rather than a horrible, distorted, out-of-proportion image like I am seeing. While I would prefer to render snapshot images, I would even be happy to see the launch image. But the option everyone mentions, ignoreSnapshotOnNextApplicationLaunch
, does not work because it only affects what you see at actual app launch time, not what is seen in the App Switcher when you double-click Home, and for many on StackOverflow it actually didn't even work at all (not even at launch time).
How do we get around this Catch-22?
This StackOverflow thread (unlike me, the OP here uses GLKit but the symptom is the same):
iOS OpenGL ES screen rotation while background apps bar visible
confirms that some OpenGL apps on iOS are able to have proper preview images in the Home double-press app switcher for both orientations. How do they do it?
How can I get proper snapshots shown in both orientations in the App Switcher?
Here is a log of AppDelegate
(appdel), ViewController
(eaglc) and View
(eaglv) calls that come from iOS at the time that I click the Home button once to exit the app. You can see the attempts at snapshotting that come well after didEnterBackground
:
+ 189.57ms appdel appWillResignActive + 0.74ms appdel appWillResignActive between_view_os_callbacks 0 + 4.11ms appdel appWillResignActive between_view_os_callbacks 0 done + 0.82ms appdel appWillResignActive activation_changed + 1.50ms appdel appWillResignActive activation_changed done + 0.47ms appdel appWillResignActive between_view_os_callbacks 1 + 2.68ms drawing rect [(144,1418)+(2,66)] (0 left) + 44.28ms swap_buffers glFlush() + 6.16ms swap_buffers presentRenderBuffer + 9.01ms appdel appWillResignActive between_view_os_callbacks 1 done + 0.61ms appdel save_state ..app saving data, no OpenGL here.. + 0.49ms appdel save_state calling glFinish + 0.34ms appdel save_state done + 0.25ms appdel appWillResignActive done + 492.72ms appdel applicationDidEnterBackground + 0.56ms appdel save_state ..app saving data, no OpenGL here.. + 0.65ms appdel save_state calling glFinish + 0.54ms appdel save_state done + 0.65ms eaglv let_go_of_frame_buffer_render_buffer app drops OpenGL frame_buffer and render_buffer here + 1.10ms appdel applicationDidEnterBackground done Now we are not supposed to do OpenGL, BUT... + 6.30ms eaglc supportedInterfaceOrientations + 5.74ms about_to_sleep between_view_os_callbacks + 1.30ms SKIPPING between_view_os_callbacks cuz app in background + 0.66ms about_to_sleep between_view_os_callbacks done + 135.85ms eaglc willRotateToInterfaceOrientation + 2.49ms appdel willChangeStatusBarFrame new=0,0 768x20 + 3.21ms appdel didChangeStatusBarFrame old=0,0 1024x20 we get a portrait layoutSubviews.... + 1.26ms eaglv layoutSubviews (initted=1, have_fbrb=0) + 1.80ms eaglv assure_frame_buffer_render_buffer + 0.95ms eaglv assure_fbrb scale ios=2 eaglv=2 + 0.90ms eaglv assure_fbrb (frame=1536,2048) + 1.04ms eaglv assure_fbrb (layer frame=1536,2048) + 0.92ms eaglv assure_fbrb in bg: will make fbrb later + 0.96ms eaglv layoutSubviews done + 3.11ms eaglc didRotateFromInterfaceOrientation + 149.07ms eaglc willRotateToInterfaceOrientation + 1.99ms appdel willChangeStatusBarFrame new=0,0 1024x20 + 2.35ms appdel didChangeStatusBarFrame old=0,0 768x20 then a landscape layoutSubviews... + 1.91ms eaglv layoutSubviews (initted=1, have_fbrb=0) + 1.09ms eaglv assure_frame_buffer_render_buffer + 0.91ms eaglv assure_fbrb scale ios=2 eaglv=2 + 1.65ms eaglv assure_fbrb (frame=2048,1536) + 0.92ms eaglv assure_fbrb (layer frame=2048,1536) + 0.93ms eaglv assure_fbrb in bg: will make fbrb later + 0.83ms eaglv layoutSubviews done + 2.79ms eaglc didRotateFromInterfaceOrientation and, adding insult to injury, we get this log message: Snapshotting a view that has not been rendered results in an empty snapshot. Ensure your view has been rendered at least once before snapshotting or snapshot after screen updates.
layoutSubviews:
? Apple states that you need to do that "Before returning from your applicationDidEnterBackground: method", which means you need to do UI updates inapplicationDidEnterBackground:
– Peden