I saw people use: [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; to handle remote control event when managing audio session.
My question:
Does this code only can be used in either a UIViewController class or AppDelegate class? Because I see everyone in Internet using it in one of the two classes. Can I use it in a class which is not subclass of UIViewController or AppDelegate?
For apps that only want to handle remote control events while in a certain state--ready to play media, for example, as opposed to being on the settings or registration screen--invoking beginReceivingRemoteControlEvents from a view controller makes a certain amount of sense. Other apps may wish to suppress remote control behavior altogether (because they take over the audio session and don't want background audio being triggered by the user and corrupting session data).
However, there's nothing that prevents you from factoring that behavior into another area of the app, especially if it is shared. beginReceivingRemoteControlEvents is a method on UIApplication, so as long as you're able to get a handle to [UIApplication sharedApplication], you can flag the app to begin/end remote control event handling.
However, it's worth noting that method is deprecated:
In iOS 7.1 and later, use the shared MPRemoteCommandCenter object to register for remote control events. You do not need to call this method when using the shared command center object. [1]
My experience with remote control event handling is a couple of years old at this point, but I recall there was some non-intuitive behavior dealing with beginReceivingRemoteControlEvents. A quick glance at MPRemoteCommandCenter makes this look like a better API for handling remote control events. If your use cases don't require iOS 7.0 support, you should investigate that API.
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; can be used at any point in your application, such as right before starting audio playback, or on launch in application:didFinishLaunchingWithOptions: delegate method.
For applications that want to use the controls for background audio playback throughout the entire application lifecycle (and backgrounded), I would recommend calling beginReceivingRemoteControlEvents in your AppDelegate. That way, you're explicitly ready to receive remote control events at any time during the application lifecycle.
Palpatim mentioned MPRemoteCommandCenter. As someone who has built a complete application around background audio playback (think radio), I would highly recommend this method over the old UIEvent callbacks. I dive into this approach in another answer, but the gist is use MPRemoteCommandCenter.sharedCommandCenter() to enable or disable playback controls on the lock screen and control center, as well as provide selectors:
let commandCenter = MPRemoteCommandCenter.sharedCommandCenter()
commandCenter.previousTrackCommand.enabled = true;
commandCenter.previousTrackCommand.addTarget(self, action: #selector(previousTrack))
commandCenter.nextTrackCommand.enabled = false
commandCenter.nextTrackCommand.addTarget(self, action: #selector(nextTrack))
Note that if you explicitly want to disable controls, you must provide a [dummy] selector for the action in addition to setting enabled = false on the command.
To address your point about becomeFirstResponder:
Any object that inherits from UIResponder, such as a UIViewController or the UIApplicationDelegate can become the first responder, as long as that object implements the required method remoteControlReceivedWithEvent: to handle the remote control events. For example, I could have the AppDelegate become the first responder and handle all remote control events in there, and send out notifications that a UIViewController listens to to pause the player or skip to the next track. You could have a UIViewController become the first responder (the controller holding a reference to the player) and control the player directly. The architecture is pretty open and it really depends on how you structured your application. You haven't provided any code, so I don't know what your player setup looks like.
Your UIResponder handling code will probably look something like this:
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
if (event.type == UIEventTypeRemoteControl) {
switch (event.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
// Pause or play action
break;
case UIEventSubtypeRemoteControlNextTrack:
// Next track action
break;
case UIEventSubtypeRemoteControlPreviousTrack:
// Previous track action
break;
case UIEventSubtypeRemoteControlStop:
// Stop action
break;
default:
// catch all action
break;
}
}
}
Again, whether you place this in a controller or in your AppDelegate is up to you.
Can I use it in a class which is not subclass of UIViewController or AppDelegate?
Yes, you implement this in any class that inherits from UIResponder. People normally use the AppDelegate or a view controller for convenience.
Related
I can't find information about how to detect when the iOS breadcrumbs are used to return to the app. I am looking to call a function on the controller when the view is active again specifically when this breadcrumb is used (Our use case being when location has been enabled externally).
I have tried using viewDidAppear but this isn't called. Is it possible? I find it unusual that this isn't called.
To be clear the breadcrumbs I am talking about are
I think you can use this method on Appdelegate
- (void)applicationDidBecomeActive:(UIApplication *)application {
}
then send a notification to your viewcontroller you want to handler.
Our app explicitly blocks user form using remote-control, e.g., old springboard from pre-iOS7, earbud, by becoming the first responder to the remote-control events. However, on iOS7, the same code fails to bypass the control centre music controls.
From out tests, control centre seems to have bypassed ALL music control events including UIEventSubtypeRemoteControlPause and UIEventSubtypeRemoteControlPlay, and UIEventSubtypeRemoteControlTogglePlayPause.
Is it that control centre has its own protocol for remote control or that the way to intercept remote-control events has changed in iOS7?
The same blocking code still works perfectly with iOS6 devices. Here is what we do:
Added a method in our appDelegate:
(BOOL)canBecomeFirstResponder {
return YES;
}
Call this in applicationDidBecomeActive:
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
// Set itself as the first responder
[self becomeFirstResponder];
Call this in applicationWillResignActive
// Turn off remote control event delivery
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
// Resign as first responder
[self resignFirstResponder];
Finally added
(void)remoteControlReceivedWithEvent:(UIEvent *)receivedEvent {
if (receivedEvent.type == UIEventTypeRemoteControl) {
switch (receivedEvent.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
NSLog(#"Received: UIEventSubtypeRemoteControlTogglePlayPause\n");
break;
case UIEventSubtypeRemoteControlPreviousTrack:
NSLog(#"Received: UIEventSubtypeRemoteControlPreviousTrack\n");
break;
case UIEventSubtypeRemoteControlNextTrack:
NSLog(#"Received: UIEventSubtypeRemoteControlNextTrack\n");
break;
case UIEventSubtypeRemoteControlPlay:
NSLog(#"Received: UIEventSubtypeRemoteControlPlay\n");
break;
case UIEventSubtypeRemoteControlPause:
NSLog(#"Received: UIEventSubtypeRemoteControlPause\n");
break;
case UIEventSubtypeRemoteControlStop:
NSLog(#"Received: UIEventSubtypeRemoteControlStop\n");
break;
default:
NSLog(#"Received: Some remove control events\n");
break;
}
}
}
Any pointer will be appreciated.
you can't block the music app. your app can become one though (apple won't like that) and then the control center would control yours
I think I have a better idea of what happened, at least at the CoreAudio level.
When the app's audio session category is solo-ambient, the music app's play event triggers an audio session interruption similar to an alarm clock or a phone call. This
will trigger app's audio session interruption listener callback with the "enter-interruption" state.
However, the music app's pause event does not trigger the listener callback with the "exit-interruption" state, as one would expect. This missing exit call effectively freezes our app's audio session. Quitting the control centre does not trigger it either. Same thing applies to a physical remote-control, except that the physical remote-control can be blocked using the firstResponder trick said in my last email. It does not work with Control Centre.
Unless I'm missing something obvious, I am more convinced that there are two bugs in either CoreAudio or other frameworks in the chain of command.
Bug 1: Audio session interruption listener's exit call cannot be made from music remote control if the entrance call is made first there.
Bug 2: Control Centre's music remote control does not conform to remote-control event mechanism.
I'm just surprised that no one ever reported this.
I think I'm going to file a bug report unless someone suggests differently.
UPDATE
Bug 2 was a false alarm. After clean rebuilding everything over iOS7 SDK for a couple times, we found that the problem went away. Bug 1 still holds.
I am currently developing an app that will need to terminate after running in the background for more than five minutes. In order to do this, I will have to have a timer running in the background after the the Home button has been pressed or in case of an interruptions such as an SMS or a telephone call, then, after five minutes the applicationWillTerminate method will be called. My first question is should I put the applicationWillTerminate in the applicationWillResignActive method or in the applicationDidEnterBackground method? My second question is since this is an app with more that one view, Should I write these things in the AppDelegate class or elsewhere? Thank you for your response.
1) You can't force your app to finish programatically.
2) You should never call these AppDelegate methods by yourself. They're meant to be called only by the system.
Reference: UIApplicationDelegate Protocol Reference.
This is pretty ghetto, but what you can do is make your app crash when you want it to exit, and it will close automatically, granted that's not closing the app, but there's no real harm in it as long as you are in control of how it crashes try to go for a bad access error, aka trying to access something that has been deallocated
as for running a timer in the background, i don't know per say if you can do that, but as an alternative you can save the time when they leave the app aka the app goes into the background and then you can have all the events return to your app of the view controller that is first responder, and each UIEvent has a time stamp, and regardless of which event it is you can compare the time stamps and see if it's greater than 5 minutes
Regardless i don't suggest any of the above, but that is the best answer i can come up with for your question
the code for receiving events out side of your app
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
will start the event tracking and the call back is:
- (void)remoteControlReceivedWithEvent:(UIEvent *)event { }
but you have to remember to
[self becomeFirstResponder];
this tells the device which view controller to go to for the event tracking, oh and don't forget to resign first responder, and endReceivingRemotecontrolEvents
I'm building a game for iOS using Cocos2D. In my game I have a pause menu that can be pulled up when playing the game. A simple tap will return from the pause menu to the game. What I would like to implement is a clean way to resume the game on the pause menu if the method applicationDidBecomeActive is called. The problem is that only the appDelegate receives the call to applicationDidBecomeActive, and my pause menu is many layers deeper than that. Right now I'm essentially passing the applicationDidBecomeActive call through about 4 different layers to get it down to the pause menu. There must be a cleaner way?
Sure is. Just add an observer for the UIApplicationDidBecomeActiveNotification from anywhere that's convenient. Application state changes can be hooked that way as well as via the app's delegate.
(Docs here.)
Read about the NSNotificationCenter
here and at Apple or just receive the UIApplicationDidBecomeActiveNotification anywhere.
take a BOOL variable in appdelegate.h with property and synthesised it then when pause button pressed from any scene set this variable to yes.
in applicationDidBecomeActive method of appdelegate check if(self.pause == YES) then don't resume ccdirector else resume it
i used this in my game and it work fine when i press pause and then press home button and when come back the app is still pause. try this
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.