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 :)
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?
I have a strange problem with state restoration for a Universal app with Split View Controller.
The strange thing that I am doing things in a very standard way using a Storyboard and segues and with a restoration identifier for alle relevant view controllers. There is not really any code, as the logic is in the Storyboard and a minimal XCode project shows this.
The problem is with a settings screen that is shown modally as a form sheet presented from the split view controller. My view controller hierarchy ends up correct, but the transition doesn't really make sense. For some reason state restoration animates the modal controller into place.
Since the screen starts out with a screenshot from the last time the app was running, with the settings controller already present, the animation is just visual noise.
I have tried to disable animation on the segue which is respected when entering the settings interactively, but when state restoration does the same thing, the animation is there.
What is the standard way to avoid this?
Calling self.window?.makeKeyAndVisible() in application(_:willFinishLaunchingWithOptions:) solved the issue for me.
More info in the docs:
Important
If your app relies on the state restoration machinery to restore its
view controllers, always show your app’s window from this method. Do
not show the window in your app’s
application:didFinishLaunchingWithOptions: method. Calling the
window’s makeKeyAndVisible method does not make the window visible
right away anyway. UIKit waits until your app’s
application:didFinishLaunchingWithOptions: method finishes before
making the window visible on the screen.
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 have a presentedViewController on top of the root view controller when the app gets dismissed. (e.g. User navigates to another app or goes back to the home screen.)
I would like to nil it out when the user reactivates the app without it being visible to them. Calling -dismissViewControllerAnimated: is not an option because it only works if the view controller is visible, and I'd like to do it sooner and specifically only in application:openURL:sourceApplication:annotation: and otherwise let the user continue their workflow in the modal view.
Are there any tricks I can use?
According to your comment, the only actual issue is what happens when the app receives application:openURL:sourceApplication:annotation: and is thus brought to the front.
In that case, simply adjust your interface in application:openURL:sourceApplication:annotation: - that is part of its purpose, to allow you to do that. You will be given some time before the "snapshot" is torn away, so if the adjustment involved is to dismiss an existing modal view, dismiss it without animation and there you are.
The user will still see the modal view for a moment, but not really - what the user is seeing is not your interface but the "snapshot" that was created by the system when the app when into the background. The "snapshot" is removed to reveal the actual interface (with a nice crossfade effect in iOS 7).
If the "snapshot" itself is problematic there are ways of preventing it from being taken, but it doesn't sound to me like this is that kind of situation.
(By the way, this raises the question of how the presented view is to know that it must be dismissed. The initial main action is in your app delegate, which is probably some conceptual distance away from the presented view. This would be an appropriate situation in which to use an NSNotification to communicate across this distance.)
I have an iPad SplitViewController application with the following features:
master (left hand) view controller is hidden in both portrait and landscape by returning YES in the shouldHideViewController delegate method
master view controller contains a tab bar controller
I utilise a multiple master detail manager to manage swapping the detail view controller based upon the selection made by the user in the master tab bar
I have a problem that only occurs after a memory warning is received by the application. When the master view slides in (either via a swipe or tapping the left bar button) the view appears effectively empty i.e. it does not contain any table view, just the semi-transparent background where the table view should be. If I tap the other view in my tab bar it loads it's master view correctly. If I then tap back to the problem view it now appears correctly.
Originally I had another problem in that not only was the view empty but it was also sized for full screen. I was able to fix the size problem via the solution proposed in this SO question - it is noted in the solution that "Apparently when a memory warning is received, the view controller gets released, so when it presents itself again, it gets it's size from it's parent view, which is full screen. So you just have to reset the frame every time it gets loaded."
By putting break points in my code I can verify that when I first try to access the master view after a memory warning the ViewDidLoad event is called (this does not occur if there has not been a memory warning), however the ViewDidAppear & ViewWillAppear events are not called. So the app is aware that it needs to reload the master view after a memory warning, but for some reason it does not seem to show the master view.
I'm struggling to work out how to correctly make the view appear again after a memory warning. Or is there something I should be doing to prevent the master view from being unloaded when a memory warning is received?
UPDATE: I have also observed that if the master view is showing at the time of the memory warning then this problem does not occur.
I raised this issue with Apple technical support. Their reply was as follows:
A search of radar shows that this is a known problem in iOS 5.1 and
that engineering is aware of it. At the moment there does not seem to be
a workaround for this problem other than making sure that you don't
get memory warnings.
If you'd like you can file an additional bug at:
http://bugreporter.apple.com/. Additionally I'd suggest that you try
this and see if it is still an issue in iOS 6. If it is please file a
bug and send me the radar number so that I can keep track of it.
I did as they suggested and tried it out in iOS 6. I found that the problem did not occur and therefore seems to be fixed in iOS 6. Apple did not offer a solution for iOS 5.1.