-[UIApplication setStatusBarHidden:withAnimation] does not fire KVO notifications for #"statusBarHidden" key - ios

I have code in my root view controller that observes the #"statusBarHidden" property of -[UIApplication sharedApplication] and adjusts its view's size in response.
When I do this, a KVO notification is fired:
[[UIApplication sharedApplication] setStatusBarHidden:YES]
But when I do this, a KVO notification is not fired:
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide]
I need to resize my view when the status bar reappears but I'm using a third-party API that invokes the latter method.
What would be the best way to handle this?

Background
As far as why you see this discrepancy in notifications firing, it probably has to do with how KVO works. You observe the value of a property. In this case, statusBarHidden. But, in order to get notifications, that property must have been changed by way of the setter that exists for it.
Notifications can't happen magically, so it effectively needs to be coded as a side-effect of the setter. (this is often done for you automatically, when coding your properties) However, the class that has that property could also choose to modify an ivar directly. In this case, UIApplication has/had an internal struct _applicationFlags, which contains
unsigned int statusBarHidden:1;
So, it's entirely possible that setStatusBarHidden:withAnimation: is just modifying the underlying data member directly, which bypasses the setter needed to callback your observer.
Solution?
In terms of workarounds for you, you don't mention whether this app is for the app store or not (could be for personal/hobby purposes, could be an Enterprise app, or could be a jailbreak app).
One thing that might be an option for you is to use method swizzling to replace the default implementation of setStatusBarHidden:withAnimation: with one of your own. Your own implementation could simply call setStatusBarHidden:, which would then re-enable KVO. Or, if you wanted to keep the animation, you could probably use GCD to schedule setStatusBarHidden: to run after the amount of time it takes setStatusBarHidden:withAnimation: to finish animating. That way, you'd still get the animation, and also have the KVO triggered by calling setStatusBarHidden:.
It's not clear to me whether method swizzling is always rejected in App Store apps. I thought it was (at least, when swizzling methods in iOS frameworks), but according to this, it is either allowed, or can slip through.
The next question would be, "if method swizzling really is something Apple wants you to avoid, despite it being in public APIs, is there a way to work around that?"
Unless the people in the Stack Overflow question I linked to were lying, it looks like it does get through review (at least sometimes). So, maybe it's not tested for in an automated way. Maybe it's sometimes visually recognized by human testers, who see a standard feature working differently, in a way that they deduce must be a result of method swizzling. In this case, you don't actually want to use it to change the status bar UI behavior, just to latch on to a notification, so that shouldn't bother them.
Or, they might be searching for selectors for known iOS APIs (used with swizzling). If that's the case, selectors built from strings can pretty easily be obfuscated, to avoid detection.
Anyway, just some options ...

I found a solution for this:
Subclass UIApplication with let's say MyUIApplication
Override setStatusBarHidden:withAnimation to call super and send a NSNotification when called
Change the line in main.m to return UIApplicationMain(argc, argv, #"MyUIApplication", NSStringFromClass([MyAppDelegate class]));
you'll get the custom notification you sent when you set status bar hidden state

Based on Or Arbel's answer I wrote this class which sends a notification for your convinience.
https://gist.github.com/hfossli/6767765

You might observe the frame of your application's main window (or one of its subviews?) instead, or override some view's layoutSubviews method.

Related

When does the SpriteKit game loop run for the first time?

I'm trying to understand when a SpriteKit scene's frame cycle runs within the main iOS run loop. Specifically, I am concerned about the AppDelegate's applicationDidBecomeActive(_:) method. I always thought that method was called after the app becomes active, but before your presented scene's frame cycle runs.
This is important to the project I am building because I use the applicationDidBecomeActive(_:) method to perform some time-sensitive tasks like examining a timestamp, setting flags, starting timers, etc. So I need to reliably anticipate when this method will be called during the frame cycle (let's just call it the "game loop").
I did some testing which suggests that the game loop runs at different times in relation to the applicationDidBecomeActive(_:) method, depending on which version of iOS the app is running on. This is a concern because it means I cannot rely on a single implementation of this method to perform the tasks I need at the correct time.
I want to know definitively when applicationDidBecomeActive(_:) is called in relation to the SpriteKit game loop. That seems like a fundamental thing that anyone writing a SpriteKit game needs to understand. And I'm shocked to see that it seems to vary depending on the OS version. It's possible that I made a mistake in my testing and assumptions. But I'll report what I found here and see if anyone else has noticed this, and if anyone can explain this odd behavior.
In my current project, I have been testing on my physical iPhone running iOS 12.4 and sometimes using the Simulator for an iPhone running iOS 13. By using print statements, I have observed that the AppDelegate's applicationDidBecomeActive(_:) method and the SKScene's update(_:) method are being called in a different order, depending on which version of iOS is used.
Note that my project uses UIViewController's viewDidLoad() method to present the scene. I tried using viewWillLayoutSubviews() instead, hoping that things might work more reliably that way. But that proved to be even less reliable, so I won't discuss that here.
Order of Method Calls (iOS 12.4):
didFinishLaunchingWithOptions
viewDidLoad
didMove
update
applicationDidBecomeActive
update
...
Order of Method Calls (iOS 13):
didFinishLaunchingWithOptions
viewDidLoad
didMove
?
applicationDidBecomeActive
update
...
You can see that both versions of the OS call AppDelegate's application(_:didFinishLaunchingWithOptions:) method first, then they load the view. In viewDidLoad(), I make my call to have the view present my SKScene. As expected, the scene's didMove(to:) method is called after the view presents the scene. But what happens next is the odd part.
In iOS 12.4, the scene's update(_:) method is called, which indicates that the scene performed a single run of its game loop. Then the AppDelegate calls its applicationDidBecomeActive(_:) method. Next, the update(_:) method runs again. Then update(_:) keeps being called over and over as the scene's game loop fires 60 times per second, as expected.
In iOS 13, the update(_:) method does not get called immediately after didMove(to:) is called. Instead, applicationDidBecomeActive(_:) is called right after didMove(to:). Only then does the update(_:) method run (and then continues running, as expected).
So basically, the issue here is that in iOS 12.4, the game loop appears to run once immediately after it is presented, before applicationDidBecomeActive(_:) is called. But in iOS 13 this does not happen.
It's a problem that the game loop in iOS 12.4 runs one extra time, before applicationDidBecomeActive(_:) is called. This makes the game's lifecycle inconsistent among different versions of the OS, and it means I will have to write different code to handle cases for different OS versions. Either that, or I must redesign the parts of the app that rely on applicationDidBecomeActive(_:) to find a more consistent way of handling them. It also makes me wonder if the extra run of the game loop is a bug in iOS 12.
I always assumed that the app's lifecycle was consistent between OS versions (at least regarding the order of method calls for AppDelegate and SKScene). But this discovery throws all of that into question. I have not yet tested with other versions of iOS, because even if this is the only discrepancy between all of the OS versions, it still means that your code must handle things differently depending on the OS version.
To add one more wrinkle to this analysis...
I also made a new SpriteKit template project and performed the same test. I found the same discrepancy, with one added peculiarity: in iOS 12.4, the update(_:) method is called twice immediately following didMove(to:), before applicationDidBecomeActive(_:) is called. In iOS 13, the behavior is the same as described above.
I'm not sure why update(_:) is firing twice rather than once as it does in my other project. That seems quite odd. But this test in a "clean" template project suggests that this is a real issue, rather than some error in my own code.
To reiterate my question...
I would like to know if anyone else has noticed this. Maybe I am mistaken in my conclusion. If this is a real issue, I wonder if there is any "fix" that can be done to make the game loop work in a consistent way for all OS versions. If not, can anyone suggest a good workaround so that your code in applicationDidBecomeActive(_:) consistently runs before the game loop first fires? I already have some ideas. But first, I want to confirm if this is an actual issue with iOS or just a mistake in my own code.
This isn't directly relevant but might give you a way forward...
SpriteKit tries to do automatic pausing and unpausing when the game goes into the background and back to the foreground. I had a situation where I wanted more control, and in particular did not want the game to always start up again on resume. My hack (which was working on iOS 12 and 13, though I don't recall versions) was to override isPaused in my derived game scene, e.g.,
override var isPaused: Bool {
get { super.isPaused }
set {
if forcePause && !newValue {
os_log("holding isPaused at true because forcePause is true", log: .app, type: .debug)
}
super.isPaused = newValue || forcePause
}
}
Then I'd have the additional variable forcePause to keep the game under control.
Perhaps you could do something similar, setting forcePause when you go into the background and then having applicationDidBecomeActive clear it. Perhaps that would be sufficient to keep iOS 12 from jumping the gun on you.
I used one of the code-level support incidents that I get as part of the Apple Developer Program to ask this question of one of their support engineers. What he told me was that there is no guarantee that update(_:) is always called after applicationDidBecomeActive(_:), so your app should not rely on such behavior. I had already implemented a workaround, and he told me I should either continue using that approach, or explore a different design that does not rely on the active state.
My workaround is:
Set a global Boolean variable called applicationDidBecomeActiveRan, initialized to false.
At the end of applicationDidBecomeActive(_:), set the global variable to true.
At the beginning of applicationWillResignActive(_:), set the global variable to false.
In the update(_:) method of the SKScene subclass, put this guard statement at the beginning of the method:
guard applicationDidBecomeActiveRan else { return }
This way, the rest of the code in my update(_:) method won't run unless applicationDidBecomeActive(_:) has already run. This ensures that the order of code execution for applicationDidBecomeActive(_:) and update(_:) will be consistent, no matter which iOS version the game is running on.
I was surprised to hear that the order of these two method calls is not guaranteed by default, because in every situation I have encountered up until this point, it has behaved in a consistent way (unless I just did not notice the inconsistency). Admittedly, I've never seen any Apple documentation that states that it should behave consistently. But when you see something happen a certain way every time, you naturally come to expect that it always works that way and indeed is supposed to work that way. It's helpful to learn that in rare edge cases like this one, it may not work in the usual way.

Where to update the UI of a notification widget just once

In my Notification Center Today Extension/Widget I need to update a portion of the UI every time Notification Center is activated. It never needs to update while Notification Center is in use, nor while it's in the background. In what method should I place that code?
viewDidLoad and viewWillAppear are both called every time it will be displayed, for example if you scroll up and down they will be called again, so that's too often.
widgetPerformUpdateWithCompletionHandler is not called at all before it's displayed for the first time it seems (at least with iOS 8.2 beta), and this method is automatically called whenever iOS feels like it to update the UI even when it's in the background which is not appropriate either.
loadView is only called a single time, never to be called again unless the widget is removed from memory. So if you open Notification Center and view the widget then dismiss Notification Center and reopen it later, it may not call that method again depending on whether or not it's been cleared from memory.
I'd just use viewDidLoad and not worry about the possibility of multiple calls. Unless the method takes a long time to run, there's no reason not to do it that way. [And if it does take a long time to run, your today extension is going to suck, so fix that.]
If for some reason you only want it to happen once, add a BOOL ivar to the class. Set it to YES in initWithCoder: Then in viewDidLoad, check that value. If it's YES, do your update and set the value to NO. If it's already NO, skip your update.

Is there any scenario that can cause ViewDidLoad to be called before didBecomeActive?

I know it's sounds silly but just to clear a point.
Is there any chance that view did load will be called before didBecomeActive ?
Is it totally impossible ?
EDIT
We have a crash that happens when user is coming back to the app from the background and we start to use openGL. The crash error points that we try to use openGL in the background.
It is important to say that our app lives in the background as a VOIP app.
We try to figure out if there is a chance that somehow we are triggering something in the background thats causes the app restart openGl in the background.
In the stack we see:
[VideoCallViewController viewDidLoad] (VideoCallViewController.m:283)
And few lines after that:
[GPUImageContext createContext]
And finally:
gpus_ReturnNotPermittedKillClient + 10
We are trying to figure out if there is a way that [VideoCallViewController viewDidLoad] was called in the background or that we must assume that we are in the foreground, and somehow moving to the background right after the viewDidLoad ?
Second option
The second option is that we are indeed moving to the background right after the viewDidLoad. The point here is that we are listening to AppWillResignActive and we pause the GPUIMage. So we can not understand why do we get the crash ?
Thanks
Thanks
When / Where do you instantiate the various GPUImage objects that you are using? Is it within viewDidLoad or possibly within init:?
It's just pure rampant speculation here since you didn't really post any code...
but if you are disposing of objects when the app heads to the background that are not then re-created when it comes back to the foreground (perhaps because the viewController was retained by a parent, and therefore init: was not called again but viewDidLoad was...) Then you may be trying to send OpenGL messages to objects that don't actually exist anymore.
On the (probably unlikely) chance that my speculation is right, you could easily fix it with the common "getter" pattern of:
- (GPUImageObjectOfInterest*)instanceOfObject {
if (!_classVariableOfThisType) {
_classVariableOfThisType = [[GPUImageObjectOfInterest alloc] init];
// custom configuration, etc...
}
return _classVariableOfThisType;
}
and then use [self instanceOfObject]; wherever you used to use _classVariableOfThisType
It's a low overhead, but reasonably foolproof way of making sure a key object exists under a wide range of app interruption / background & foreground & low memory conditions.
Don't be shy to post too much code though, we can read through an entire class if needed. Some of us like reading code! (and it will really help the quality of response you get...)

Macro Recording in iOS

Is it possible to record set of touch events on iPhone and then playback?
I have searched alot but could not find any answer. if its possible, can anyone explain with an example.
I m not looking for testing purpose. Within my application, instead of creating animation, i just want to record set of events and then want to playback to explain the app flow to the users.
Regards.
Recording is pretty simple. Look at the various "Responding to Touch Events" and "Responding to Motion Events" methods on UIResponder. Just create your own UIView subclass (since UIView inherits from UIResponder) and keep a copy of the events passed into the relevant methods.
Playback is a bit more complicated; there's no way to make UITouch or UIEvent objects (so you can't make a fake event and pass it on to -[UIApplication sendEvent:]). But, there's nothing stopping you from manually parsing an array of Event objects and handling it on your own (aside from it being some kind of ugly code).
There's no built-in macro capability, but you could certainly build that ability into your application. You'll need to do more than just play back events, though. Touches aren't normally visible, but if you're trying to explain how to use your app to the user you'll probably want to have some sort of visual representation for the touches that trigger different responses similar to the way the iOS Simulator uses white dots to represent multiple touches when you hold down the option key.
Assuming that you can solve that problem, two strategies for easily recording user actions come to mind:
Use the Undo Manager: NSUndoManager is already set up to "record" undoable events. If you invest some time into making everything in your app undoable, you could (maybe) perform a set of actions, undo them all to move them to the redo stack, and then save the events in the redo stack as your script.
Use Accessibility: The Accessibility framework sends notifications whenever user interface elements are touched. Your app could use those notifications to create a playback script. You'll still need to write the code to play back the events in the script, though.
You could mirror your application with AirServer and use any screen capture software to make the video.

UILocalNotifcation custom fire event, or struggle on with NSTimer

I have a state-transition problem with NSTimer, of which I find difficult to keep track of during applicationWillResignActive / applicationDidEnterBackground, according to the context of my app.
I was wondering if it might not be a better idea to utilise UILocalNotification, especially given it's background/inactive firing. However, I wanted to know whether we have the ability to provide a custom method to UILocalNotification, of which does not present a dialog box (would damage the whole point of my app). In effect, i'd like to only make use of the timer-fire capabilities of UILocalNotification, and handle the fire event with my own method which does something very "undialog-friendly"
Have checked the ADC docs and it alludes to the dialog being presented every time.
Any advice you can give on this would be appreciated.
thanks
sc.
The dialog box is presented when your app is in the background. But it is not presented when your app is running - instead your app is free to deal with the notification however it sees fit. So it would be perfectly possible to hook it up to a custom method of your own making.
The main reason for this behaviour is a user may not want to go into your app if it's in the background. Of course, with iOS 5 the notification may not be a dialog box - it could be one of the new notification styles.

Resources