iOS event/notification for network activity up/down/off - ios

I want a event/callback for my iOS app when the network activity goes from none to up (and the other way around). Similar to how Android does with onDataActivity(). I'm not talking about Reachability but when data actually starts or stops transmitting.
The app is not for App Store and not for jailbreak. I have got a similar functionality of detecting when the screen goes on/off working by using
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
NULL, // observer
displayStatusChanged, // callback
CFSTR("com.apple.iokit.hid.displayStatus"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
along with other events such as
com.apple.springboard.hasBlankedScreen
com.apple.springboard.lockstate
Now I wonder if there is a event for when data is started or stopped to transmit? Or if someone could point me the direction of a complete list of all events that can be monitored in the way above.

I monitored both the standard Darwin notifications and the Core Telphony notifications on a jailbroken iOS 5 iPhone.
I did not see any notifications that really do what you want.
There are a few Core Telephony notifications that come out, but not on every transmit start and end. It looks like when the data service connects, there might be some notifications, but again, they really aren't exactly what you asked for:
kCTIndicatorRadioTransmitNotification
kCTRegistrationDataStatusChangedNotification
If you want to try monitoring all the Core Telephony notifications for yourself, you can use the Core Telephony framework, and the CT notification center:
-(void) registerCallback {
id ct = CTTelephonyCenterGetDefault();
CTTelephonyCenterAddObserver(ct, // center
NULL, // observer
telephonyEventCallback, // callback
NULL, // event name (or all)
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
}
static void telephonyEventCallback(CFNotificationCenterRef center, void* observer, CFStringRef name, const void* object, CFDictionaryRef userInfo)
{
//NSLog(#"telephonyEventCallback()");
NSString* notifyName = (__bridge NSString*)name;
if ([notifyName isEqualToString:#"kCTMessageReceivedNotification"]) { // received SMS
} /* look for other notification names here */
}
In the above call, I pass NULL to the CTTelephonyCenterAddObserver() call, which registers for all notifications. You can of course pass the name of one particular notification if you know what you're looking for, like the example you posted for com.apple.iokit.hid.displayStatus.
Regarding john.k.doe's option, you might try using Key Value Observing on that property, to get notified when it changes:
UIApplication* app = [UIApplication sharedApplication];
[app addObserver: self forKeyPath: #"networkActivityIndicatorVisible" options: NSKeyValueObservingOptionNew context: nil];
where your observer callback is:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"networkActivityIndicatorVisible"]) {
// process here
NSLog(#"network activity indicator change!");
BOOL active = [UIApplication sharedApplication].networkActivityIndicatorVisible;
}
}
I'm not sure if KVO will still work in the background, and it might depend on how your app manages being backgrounded.
But, of course, that requires apps to actually use that property when they access the network, which not all of them do. It's unfortunate that Apple even made that indicator something that 3rd-party developers need to control. On Android and BlackBerry, the OS is smart enough to know when it's transmitting/receiving.
So, this is still only partly what you need :(

there's really not a system Notification for this.
if you are talking about monitoring activity to/from your own app, you obviously have control of that, and should be able to bracket any calls you know to access the network …
if you are talking about monitoring activity to/from all other apps, and you are willing to presume that they are in strict compliance with Apple guidelines and turn on/off the network activity status indicator in the status bar, you can call:
[[UIApplication sharedApplication] isNetworkActivityIndicatorVisible];
but this obviously requires that you do so in a polling fashion, probably best done on a background thread / GCD queue. then you can post your own notification, to be seen elsewhere within your app.

Related

how does "idleTimerDisabled" work in didFinishLaunchingWithOptions?

I'm not sure how does the below code work in the didFinishLaunchingWithOptions in appdelegate ?
[[UIApplication sharedApplication] addObserver: self forKeyPath: #"idleTimerDisabled" options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context: nil];
what does it exactly do with "idleTimerDisabled" ?
thanks
Apple Documentation for idleTimerDisabled
The default value of this property is NO. When most apps have no touches as user input for a short period, the system puts the device into a "sleep” state where the screen dims. This is done for the purposes of conserving power. However, apps that don't have user input except for the accelerometer—games, for instance—can, by setting this property to YES, disable the “idle timer” to avert system sleep.
By assigning true to this value, iOS won't dim the screen and lock the iPhone when user doesn't make any action (touches, press, scroll, etc...). Example of this can be found in games vs other normal apps. Games make your iPhone awake for a lot longer than other apps.
For
[[UIApplication sharedApplication] addObserver: forKeyPath: options: context:]
This is an Objective-c key value observing aka KVO.
What your code means is that, when someone assigns or make changes to UIApplication.sharedApplication.idleTimerDisabled with an arbitrary value, true or false in this case, you wish to receive an invocation about the assignment or change in [self observeValueForKeyPath: ofObject: change: context:] method signature.
The option NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew means, you want to receive more information about changed old value and new value in the change dictionary which you can access later in the observing method.
For more information about KVO, checkout this post by NSHipster.
AppCoda also has a nice explanation for this.
Conclusion
What your line of code means is, you want to receive a notification about the changes made to UIApplication.shared.idleTimerDisabled attribute in KVO observing method and you want to access old value and new value through change dictionary.

Xamarin Forms: Have I Covered the bases on iOS Push Notifications?

Issue: Different Behavior In 3 Different Contexts
Ok so Ok, in iOS it seems three different things can happen regarding Push Notifications:
When a Push Notification is received when the app is not in the foreground
something shows up in Notification Center
if the app is opened by tapping the notification, either AppDelegate.DidReceiveRemoteNotification(...) or AppDelegate.ReceivedRemoteNotification(...) is called, apparently depending on which one is implemented (??).
if the app is opened without tapping the notification, only AppDelegate.WillEnterForeground(...), is called, without any explicit mention of the notification, and nothing else happens to acknowledge that a notification was received.
When a Push Notification is received when the app is in the foreground it causes the UNUserNotificationCenterDelegate, if there is one, to execute UNUserNotificationCenterDelegate.WillPresentNotification(...).
Approach: Routing To One Method From All Contexts
So to cover all bases with Push I need to implement something in all three methods: AppDelegate.DidReceiveRemoteNotification(...) / AppDelegate.ReceivedRemoteNotification(...), AppDelegate.WillEnterForeground(...), and UNUserNotificationCenterDelegate .WillPresentNotification(...).
Here are some stubs to show my approach to all this.
First, I created a custom UNUserNotificationCenterDelegate, with a Shared static member:
public class IncomingNotificationHandler : UNUserNotificationCenterDelegate
{
public static IncomingNotificationHandler Shared = new IncomingNotificationHandler();
...
}
Second, inside that class I made a handler that I can route to in every case (again, this is just a stub for debugging purposes):
//sets all parameters to null by default, so it can be called from methods
//that don't know anything about notifications:
public void HandleNotificationsIfAny(UIApplication application = null,
NSDictionary userInfo = null,
Action<UIBackgroundFetchResult> completionHandler = null)
{
//checks if userInfo is null, and logs its conclusions about that:
if (userInfo == null)
{
//In the null case, we can get pending notifications from
//UNUserNotificationCenter:
UNNotification[] pendingNotifications = new UNNotification[] { };
UNUserNotificationCenter.Current.GetDeliveredNotifications(returnedValue => pendingNotifications = returnedValue);
//Then we log the number of pending notifications:
Debug.WriteLine("IncomingNotificationHandler: HandleNotificationsIfAny(...): delivered notification count: " + pendingNotifications.Length);
//And make note of where this was probably called from:
Debug.WriteLine("IncomingNotificationHandler: HandleNotificationsIfAny(...): may have been called from this.WillPresentNotification(...) OR AppDelegate.WillEnterForeground(...)");
return;
});
}
else
{
//In the non-null case, we log the userInfo
Debug.WriteLine("IncomingNotificationHandler: HandleNotificationsIfAny(...): just got info: " + userInfo);
//And make note of where this was probably called from:
Debug.WriteLine("IncomingNotificationHandler: HandleNotificationsIfAny(...): may have been called from AppDelegate.DidReceiveRemoteNotification(...)");
}
}
Third, inside the same class, I implemented the single method that's required by UNUserNotificationCenterDelegate, and I routed to the handler from it:
public override void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
{
HandleNotificationsIfAny();
}
Fourth, and last, inside AppDelegate, I routed to the same handler from both relevant methods:
//I prefer using DidReceiveRemoteNotification because in my experience
//the other one is sometimes not reliable:
public override void DidReceiveRemoteNotification(UIApplication application,
NSDictionary userInfo,
Action<UIBackgroundFetchResult> completionHandler)
{
//Simply passing on all the parameters called in this method:
IncomingNotificationHandler.Shared.HandleNotificationsIfAny(application, userInfo, completionHandler);
}
//WillEnterForeground also calls the handler without any parameters
//because it doesn't automatically know anything about notifications:
public override void WillEnterForeground(UIApplication application)
{
IncomingNotificationHandler.Shared.HandleNotificationsIfAny();
}
With that, as it stands, I think I'm handling a notification event in the same way no matter how my app is alerted about it, and even when it's not alerted at all.
Does anyone know if I now have it covered, or if there's some other cases I need to handle?
For the first scenario: AppDelegate.ReceivedRemoteNotification
It reflects the objective c method: application:didReceiveRemoteNotification:, but this event has been deprecated since iOS 10: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623117-application?language=objc. So I think there's no need to handle this event.
For the second scenario: AppDelegate.DidReceiveRemoteNotification
You can still utilize it to handle notifications now if you haven't implemented UNUserNotificationCenter and please notice it is only valid after iOS 7+. Moreover, this event will be triggered when app is on the foreground and if your app is on the background, this event only fires when the user clicks the notification to open your application. And there's no way to access the notification's information if the user clicks the icon to open the app.
I don't think handling AppDelegate.WillEnterForeground is a good approach, as it will be called each time the app resumes from background to foreground even though there are no notifications.
For the scenario: UNUserNotificationCenterDelegate
You could only use this feature after iOS 10. Once you have implemented it on the device iOS 10+, DidReceiveRemoteNotification and ReceivedRemoteNotification will never be triggered. WillPresentNotification will be called when app is on the foreground. DidReceiveNotificationResponse will be fired when the app is on the background and user clicks notifications to open it.
As a conclusion, if you want to easily handle the notification AppDelegate.DidReceiveRemoteNotification is enough. If you want to consume the new features of UNUserNotificationCenter, AppDelegate.DidReceiveRemoteNotification and UNUserNotificationCenter should be both involved. The prior one for the iOS 7+ devices and the later one for iOS 10+ devices.
Update:
For iOS 10+, you could use UNUserNotificationCenter.Current.GetDeliveredNotifications to obtain the notifications that are still displayed in Notification Center. And if you only want to support iOS version 10 and later. I think UNUserNotificationCenter is enough, there's no need to implement AppDelegate.DidReceiveRemoteNotification(...) or AppDelegate.ReceivedRemoteNotification(...).
If the app is on background / killed state and the user clicks notification to
open the app, DidReceiveNotificationResponse will be called.
If the
user clicks icon to open your app and the app is killed you should
place your logic code in FinishedLaunching.
If the user clicks icon
to open your app and app is on background, you can handle
WillEnterForeground as you did before.
If the app is on foreground,
handle WillPresentNotification.

How do I replace MPMoviePlayer notifications?

In iOS 9 MPMoviePlayer and all his components are deprecated.
We used MPMoviePlayerController notifications, like MPMoviePlayerLoadStateDidChangeNotification, MPMovieDurationAvailableNotification, MPMoviePlayerPlaybackStateDidChangeNotification, MPMoviePlayerReadyForDisplayDidChangeNotification, to track video service quality. But now with AVPlayerViewController I can't find properly replacement for these notifications.
How do I replace these notifications now?
AVPlayerViewController is a lot different in its usage from the MPMoviePlayerViewController. Instead of using notifications you use Key Value Observing to determine the current characteristics of the AVPlayer object associated with the AVPlayerViewController. According to the docs:
You can observe the status of a player using key-value observing. So
that you can add and remove observers safely, AVPlayer serializes
notifications of changes that occur dynamically during playback on a
dispatch queue. By default, this queue is the main queue (see
dispatch_get_main_queue). To ensure safe access to a player’s
nonatomic properties while dynamic changes in playback state may be
reported, you must serialize access with the receiver’s notification
queue. In the common case, such serialization is naturally achieved by
invoking AVPlayer’s various methods on the main thread or queue.
For instance, if you want to know when your player has been paused add an observer on the rate property of the AVPlayer object:
[self.player addObserver:self forKeyPath:#"rate" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context: &PlayerRateContext];
Then in the observe method check if the new value is equal to zero:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if (context == &PlayerRateContext) {
if ([[change valueForKey:#"new"] integerValue] == 0) {
// summon Sauron here (or whatever you want to do)
}
return;
}
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
return;
}
A lot of properties on the AVPlayer are observable. Go through the Class reference.
Also apart from this there are several Notifications available for the AVPlayerItem object which are limited but still helpful.
Notifications
AVPlayerItemDidPlayToEndTimeNotification
AVPlayerItemFailedToPlayToEndTimeNotification
AVPlayerItemTimeJumpedNotification
AVPlayerItemPlaybackStalledNotification
AVPlayerItemNewAccessLogEntryNotification
AVPlayerItemNewErrorLogEntryNotification
I find AVPlayerItemDidPlayToEndTimeNotification particularly useful to seek the Item to the start once playback has finished.
Using these two options together you should be able to replace most if not all notifications for the MPMoviePlayerController
I looked at the documentation for both MPMoviePlayerNotifications and AVPlayerItemNotifications and I notice two things.
MPMoviePlayerNotifications don't show they were deprecated:
AVPlayerItemNotifications don't have any replacements that I could see:
So, I am confused that you are saying MPMoviePlayerNotifications are deprecated, because the docs are saying they are available. Also, I don't think AVPlayerItemNotifications has a replacement for MPMoviePlayerNotifications.

NSNotificationCenter callback while app in background

One question and one issue:
I have the following code:
- (void) registerForLocalCalendarChanges
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(localCalendarStoreChanged) name:EKEventStoreChangedNotification object:store ];
}
- (void) localCalendarStoreChanged
{
// This gets call when an event in store changes
// you have to go through the calendar to look for changes
[self getCalendarEvents];
}
These methods are in a class/object called CalendarEventReporter which contains the method getCalendarEvents (in the callback).
Two things:
1) If the app is in the background the callback does not run. Is there a way to make it do that?
2) When I bring the app back into the foreground (after having changed the calendar on the device) the app crashes without any error message in the debug window or on the device. My guess is that the CalendarEventReporter object that contains the callback is being garbage-collected. Is that possible? Any other thoughts on what might be causing the crash? Or how to see any error messages?
1) In order for the app to run in the background you should be using one of the modes mentioned in the "Background Execution and Multitasking section here:
uses location services
records or plays audio
provides VOIP
services
background refresh
connection to external devices
like through BLE
If you are not using any of the above, it is not possible to get asynchronous events in the background.
2) In order to see the crash logs/call stack place an exception breakpoint or look into the "Device Logs" section here: Window->Organizer->Devices->"Device Name" on left->Device Logs on Xcode.
To answer your first question, take a look at https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html
What I did to get code running in the background is to do something like
In the .h file
UIBackgroundTaskIdentifier backgroundUploadTask;
In the .m file
-(void) functionYouWantToRunInTheBackground
{
self.backgroundUploadTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
//code to do something
}
-(void) endBackgroundUpdateTask
{
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUploadTask];
self.backgroundUploadTask = UIBackgroundTaskInvalid;
}
The code above I pretty much learned from objective c - Proper use of beginBackgroundTaskWithExpirationHandler
As for your second question, you should set a breakpoint where code is supposed to run when you bring the app back to the foreground. No one can figure out why an app crashes if not given enough code or information.
The solution to the second part of the question was to raise the scope of the object containing the callback code. I raised it to the level of the containing ViewController. This seems to work. I still can't figure out how to raise the Notification (i.e. execute the call back) if the notification comes while the app is in the background/suspended. This prevented the object containing the callback from being cleaned up.

Firebase PresenceManaging iOS

I'm implementing a system based on firebase docs:
[connectionMonitor observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
if([snapshot.value boolValue]) {
// connection established (or I've reconnected after a loss of connection)
// add this device to my connections list
// this value could contain info about the device or a timestamp instead of just true
Firebase * con = [[Firebase alloc]initWithUrl:[NSString stringWithFormat:#"%#Users/%#/connections/", urlString, currentUserId]];
Firebase * newConnection = [con childByAutoId];
[newConnection setValue:#YES];
// when this device disconnects, remove it
[newConnection onDisconnectRemoveValue];
}
}];
Which works fine, if the user fully disconnects, but that's my problem.
I use this system to see if the user is online. If they're not online, I trigger a push notification. If the user closes the app, firebase doesn't disconnect, but it also doesn't receive updates, so on the other end, the user looks like they are still online. For the firebase onDisconnect value to properly set, the user is required to completely close out of the app.
I have resolved this by adding:
- (void)applicationWillResignActive:(UIApplication *)application
{
[Firebase goOffline];
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[Firebase goOffline];
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[Firebase goOnline];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[Firebase goOnline];
}
Is this normal behavior, or am I doing something wrong?
This is (currently) expected behavior. Firebase won't trigger the presence actions until the client actually disconnects and iOS will leave the underlying socket connection alive for some period of time (probably less than 5 minutes) after the app goes to the background... so presence will be delayed. It should still definitely happen eventually though.
Your workaround should work fine, or to avoid tearing down the whole connection, you could just set the presence bit to #NO / #YES on going to background / foreground.
I can see how most apps would expect presence to kick in when the app goes to the background, so we may investigate changing this behavior in the future.

Resources