Alright im a bit stuck on how to work this.
First ill show you the code.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
NSString *badge = [apsInfo objectForKey:#"badge"];
NSLog(#"Received Push Badge: %#", badge);
application.applicationIconBadgeNumber = [[apsInfo objectForKey:#"badge"] integerValue];
}
sorry for the abundance of mess, the Code button was not working.
Now my push gateway provides a number each time for how many alerts are being sent, etc, but if there are previous alerts, how would i get this code to just add +1 to the list instead of just setting the new number
APNS doesn't support increment operations for badges; each push notification generated should be setting what the current value should be. (Mainly due to the fact that push notifications aren't guaranteed to be received by a device)
So, you'll need to have a service/server somewhere keeping track of what badges should be for each of your users, unfortunately.
You should try this:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
NSString *badge = [apsInfo objectForKey:#"badge"];
NSLog(#"Received Push Badge: %#", badge);
int currentBadgeNumber = application.applicationIconBadgeNumber;
currentBadgeNumber += [[apsInfo objectForKey:#"badge"] integerValue];
application.applicationIconBadgeNumber = currentBadgeNumber;
}
Related
I have developed a chat application with push notification service. I have tested it on development mode and adhoc mode . Every device is generating device token successfully. But in production mode, report comes that some of the device could not be registered to APNS . I don't know the reason why it is happening. Each and every device on which i am testing, successfully registered with APNS but some of the devices couldn't be registered. Anyone have idea about it? I am stuck and could not find the reason behind it . My code for registering to APNS is-
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[UIApplication sharedApplication]registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert|UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeSound ];
// Handle launching from a notification
UILocalNotification *locationNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (locationNotification) {
// Set icon badge number to zero
application.applicationIconBadgeNumber = 0;
}
//some other code//
return YES;
}
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken: (NSData *)deviceToken
{
globalManager.deviceToken = [[[[deviceToken description]
stringByReplacingOccurrencesOfString: #"<" withString: #""]
stringByReplacingOccurrencesOfString: #">" withString: #""]
stringByReplacingOccurrencesOfString: #" " withString: #""];
}
-(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError: (NSError *)error
{
globalManager.deviceToken=nil;
NSLog(#"fail reg");
}
As #Andrey pointed in the comments above, you should really examine error passed to you app delegate's application:didFailToRegisterForRemoteNotificationsWithError:. It will likely contain the reason for the failure.
Reasons for such failure may range from users not allowing your app to use push notifications, to network problems, to something else.
Im using MCNearbyServiceBrowser and MCNearbyServiceAdvertiser to join two peers to a MCSession. I am able to send data between them using MCSession's sendData method. All seems to be working as expected until I randomly (and not due to any event I control) receive a MCSessionStateNotConnected via the session's MCSessionDelegate didChangeState handler. Additionally, the MCSession's connectedPeers array no longer has my peers.
Two questions: Why? and How do i keep the MCSession from disconnecting?
This is a bug, which I just reported to Apple. The docs claim the didReceiveCertificate callback is optional, but it's not. Add this method to your MCSessionDelegate:
- (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
{
certificateHandler(YES);
}
The random disconnects should cease.
UPDATE After using a support ticket to Apple, they confirmed that calling sendData too often and with too much data can cause disconnects.
I have had disconnects when hitting break points and when backgrounding. Since the break points won't happen on the app store, you need to handle the backgrounding case by beginning a background task when your app is about to enter the background. Then end this task when your app comes back to the foreground. On iOS 7 this gives you about 3 background minutes which is better than nothing.
An additional strategy would be to schedule a local notification for maybe 15 seconds before your background time expires by using [[UIApplication sharedApplication] backgroundTimeRemaining], that way you can bring the user back into the app before it suspends and the multi peer framework has to be shutdown. Perhaps the local notification would warn them that their session will expire in 10 seconds or something...
If the background task expires and the app is still in the background, you have to tear down everything related to multi-peer connectivity, otherwise you will get crashes.
- (void) createExpireNotification
{
[self killExpireNotification];
if (self.connectedPeerCount != 0) // if peers connected, setup kill switch
{
NSTimeInterval gracePeriod = 20.0f;
// create notification that will get the user back into the app when the background process time is about to expire
NSTimeInterval msgTime = UIApplication.sharedApplication.backgroundTimeRemaining - gracePeriod;
UILocalNotification* n = [[UILocalNotification alloc] init];
self.expireNotification = n;
self.expireNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:msgTime];
self.expireNotification.alertBody = TR(#"Text_MultiPeerIsAboutToExpire");
self.expireNotification.soundName = UILocalNotificationDefaultSoundName;
self.expireNotification.applicationIconBadgeNumber = 1;
[UIApplication.sharedApplication scheduleLocalNotification:self.expireNotification];
}
}
- (void) killExpireNotification
{
if (self.expireNotification != nil)
{
[UIApplication.sharedApplication cancelLocalNotification:self.expireNotification];
self.expireNotification = nil;
}
}
- (void) applicationWillEnterBackground
{
self.taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
{
[self shutdownMultiPeerStuff];
[[UIApplication sharedApplication] endBackgroundTask:self.taskId];
self.taskId = UIBackgroundTaskInvalid;
}];
[self createExpireNotification];
}
- (void) applicationWillEnterForeground
{
[self killExpireNotification];
if (self.taskId != UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:self.taskId];
self.taskId = UIBackgroundTaskInvalid;
}
}
- (void) applicationWillTerminate
{
[self killExpireNotification];
[self stop]; // shutdown multi-peer
}
You'll also want this handler in your MCSession delegate due to Apple bug:
- (void) session:(MCSession*)session didReceiveCertificate:(NSArray*)certificate fromPeer:(MCPeerID*)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
{
if (certificateHandler != nil) { certificateHandler(YES); }
}
There are many causes of this, and the two answers thus far are both correct in my experience. Another which you'll find in other similar questions is this: Only one peer can accept another's invitation.
So, to clarify, if you set up an app where all devices are both advertisers and browsers, any devices can freely invite any others found to join a session. However, between any two given devices, only one device can actually accept the invitation and connect to the other device. If both devices accept each others' invitations they will disconnect within a minute or less.
Note that this limitation does not prevent the desired behavior because - unlike what my intuition stated before I built my multipeer implementation - when one device accepts an invitation and connects to another device they both become connected and receive connection delegate methods and can send each other messages.
Therefore, if you are connecting devices which both browse and advertise, send invitations freely but only accept one of a pair.
The problem of only accepting one of two invitations can be solved a myriad of ways. To begin, understand that you can pass any arbitrary object or dictionary (archived as data) as the context argument in an invitation. Therefore, both devices have access to any arbitrary information about the other (and of course itself). So, you could use at least these strategies:
simply compare: the display name of the peerID. But there's no guarantee these won't be equal.
store the date your multipeer controller was initialized and use that for comparison
give each peer a UUID and send this for comparison (my technique, in which each device - indeed each user of the app on a device - has a persistent UUID it employs).
etc - any object which supports both NSCoding and compare: will do fine.
I've been having similar problems. It seems though that if I have run my app on one iOS device, and connected to another, then quit and relaunch (say when I rerun from Xcode), then I am in a situation where I get a Connected message and then a Not Connected message a little later. This was throwing me off. But looking more carefully, I can see that the Not Connected message is actually meant for a different peerId than the one that has connected.
I think the problem here is that most samples I've seen just care about the displayName of the peerID, and neglect the fact that you can get multiple peerIDs for the same device/displayName.
I am now checking the displayName first and then verifying that the peerID is the same, by doing a compare of the pointers.
- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {
MyPlayer *player = _players[peerID.displayName];
if ((state == MCSessionStateNotConnected) &&
(peerID != player.peerID)) {
NSLog(#"remnant connection drop");
return; // note that I don't care if player is nil, since I don't want to
// add a dictionary object for a Not Connecting peer.
}
if (player == nil) {
player = [MyPlayer init];
player.peerID = peerID;
_players[peerID.displayName] = player;
}
player.state = state;
...
I was disconnecting immediately after I accepted the connection request. Observing the state, I saw it change from MCSessionStateConnected to MCSessionStateNotConnected.
I am creating my sessions with:
[[MCSession alloc] initWithPeer:peerID]
NOT the instantiation method dealing with security certificates:
- (instancetype)initWithPeer:(MCPeerID *)myPeerID securityIdentity:(NSArray *)identity encryptionPreference:(MCEncryptionPreference)encryptionPreference
Based on Andrew's tip above, I added the delegate method
- (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler {
certificateHandler(YES);
}
and the disconnects stopped.
MKNetworkKit allegedly supports resuming of interrupted downloads, but it's not clear how one would go about this. In another thread, it's developer says it works if the server sends Range headers.
How to cancel or pause download operation of MKNetworkKit iOS?
However, it's my understanding that it is the client that sends Range headers. I would expect the library to see how much has been downloaded, and then request the appropriate range. I don't see any place in the code that does this.
The method
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
does indeed check if a Range has been specified, but there seems to be no code actually doing the specifying.
Has anyone gotten MKNetworkKit to resume a download after network failure?
Outside of the freezable option, which as I understand doesn't work for GET, there isn't a way in the standard MKNetworkKit to resume a download. However, a slight modification does allow you to resume a download that has either been stopped by
beginBackgroundTaskWithExpirationHandler
or by
operationFailedWithError
it requires saving downloadedDataSize to another property say resumeDownloadedDataSize and making this available to the calling object via a notification such as
NSDictionary *resumeDownloadedDataSizeDetails = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithLong:self.resumeDownloadedDataSize], #"resumeDownloadedDataSize", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kMKNetworkOperationEndBackgroundTaskWithExpirationHandler object:self userInfo:resumeDownloadedDataSizeDetails];
(define kMKNetworkOperationEndBackgroundTaskWithExpirationHandler in MkNetworkKit.h)
Set resumeDownloadedDataSize to 0 where the operation completes without error or interruption and make it equal to downloadDataSize when you want to resume.
Then add
if (self.resumeDownloadedDataSize !=0) {
NSString* range = #"bytes=";
range = [range stringByAppendingString:[[NSNumber numberWithLong:self.resumeDownloadedDataSize] stringValue]];
range = [range stringByAppendingString:#"-"];
[[self request] setValue:range forHTTPHeaderField:#"Range"];
}
to
-(void) start
This code at the start of
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
if (self.downloadedDataSize == 0) {
// This is the first batch of data
// Check for a range header and make changes as neccesary
NSString *rangeString = [[self request] valueForHTTPHeaderField:#"Range"];
if ([rangeString hasPrefix:#"bytes="] && [rangeString hasSuffix:#"-"]) {
NSString *bytesText = [rangeString substringWithRange:NSMakeRange(6, [rangeString length] - 7)];
self.startPosition = [bytesText integerValue];
self.downloadedDataSize = self.startPosition;
}
}
Now works for my added offset as the client has requested a range.
I also added
#property (assign, nonatomic) NSUInteger resumeDownloadedDataSize;
to the header file so I could set it from other views.
Now in your calling object you can listen for the notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didMKNetworkOperationEndBackgroundTaskWithExpirationHandler:) name:kMKNetworkOperationEndBackgroundTaskWithExpirationHandler object:nil];
check it's for you and restart the operation setting a range.
- (void) didMKNetworkOperationEndBackgroundTaskWithExpirationHandler: (NSNotification*) notification
{
if ([[notification object] isEqual:downloadOperation]) {
resumeDownloadedDataSize = [notification.userInfo objectForKey:#"resumeDownloadedDataSize"];
bDownloadOperationCancelled=YES;
}
}
As I am using this for a large file download, the expiration handler expires and I restart the operation when I come back into foreground.
if (bDownloadOperationCancelled) {
NSLogDebug(#"DownloadOperationCancelled restarted");
[self doFileDownload];
bDownloadOperationCancelled=NO;
}
where
- (void) doFileDownload
{
downloadOperation = [ApplicationDelegate.downloadEngine downloadVideoFile:authDownloadURLString toFile:downloadPath withOffset:resumeDownloadedDataSize];
(Your code for handling completion blocks etc)
…
}
The final piece is to make sure in your engine you set append to YES
[op addDownloadStream:[NSOutputStream outputStreamToFileAtPath:filePath append:YES]];
So a resume adds to the file.
Just to also be clear I am using AWS CloudFront and this supports the use of range headers when data is stored in S3.
This appears to work for me. I am sure there are more elegant ways of doing it.
I have a problem below:
When minimize app to background by press home button, create a local notification for pop-up every 5 minutes.
Remove app from background.
-->My expected then pup-up just only show when app exist and it's discarded when remove app from background.
My issue that local notification still active and it still showing pop-up every 5 minutes after remove it from background.
How can i stop it?
Please help me!
Thanks in advanced.
Put this in application delegate. It will remove all local notifications when the application enters background.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[[UIApplication sharedApplication] cancelAllLocalNotifications];
}
If you don't want to cancel all notifications... I've set up a unique identifier stored in the notification's userInfo dictionary. When I want to delete I fast enumerate through all notifications and pick out the correct one for deletion.
My stumbling blocks here were remembering to store the UUID I'd created for the notification and also remembering to use isEqualToString in the fast enumeration. I guess I could also have used a specific name string instead of a unique identifier. If anyone can let me know a better method than fast enumerating please let me know.
#interface myApp () {
NSString *storedUUIDString;
}
- (void)viewDidLoad {
// create a unique identifier - place this anywhere but don't forget it! You need it to identify the local notification later
storedUUIDString = [self createUUID]; // see method lower down
}
// Create the local notification
- (void)createLocalNotification {
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
if (localNotif == nil) return;
localNotif.fireDate = [self.timerPrototype fireDate];
localNotif.timeZone = [NSTimeZone defaultTimeZone];
localNotif.alertBody = #"Hello world";
localNotif.alertAction = #"View"; // Set the action button
localNotif.soundName = UILocalNotificationDefaultSoundName;
NSDictionary *infoDict = [NSDictionary dictionaryWithObject:storedUUIDString forKey:#"UUID"];
localNotif.userInfo = infoDict;
// Schedule the notification and start the timer
[[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
}
// Delete the specific local notification
- (void) deleteLocalNotification {
// Fast enumerate to pick out the local notification with the correct UUID
for (UILocalNotification *localNotification in [[UIApplication sharedApplication] scheduledLocalNotifications]) {
if ([[localNotification.userInfo valueForKey:#"UUID"] isEqualToString: storedUUIDString]) {
[[UIApplication sharedApplication] cancelLocalNotification:localNotification] ; // delete the notification from the system
}
}
}
// Create a unique identifier to allow the local notification to be identified
- (NSString *)createUUID {
CFUUIDRef theUUID = CFUUIDCreate(NULL);
CFStringRef string = CFUUIDCreateString(NULL, theUUID);
CFRelease(theUUID);
return (__bridge NSString *)string;
}
Most of the above has probably been lifted from StackOverflow at sometime in the last 6months. Hope this helps
I have developed server side application to maintain the badge number as increment or decrement after receiving new notification and delete after seeing notification it works fine.
But there is some problem in showing the badge, the actual scenario is - After getting new notification on device, I am click on cancel button then badge number shows correctly but after that I will open the application and close the application badge will be removed. That means I am not sending request to the server that notification was seen by me and now you can decrement the badge by one. Then also badge removed from app icon.
My question is that when we open the application then badge number automatically removed from (application) device? or it will shows as it is until we set to zero?
It will show until you set it to zero and you can do it with the following code:
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]
EDIT:
It is more common to set the badge number as you receive the notification, in either application:didReceiveRemoteNotification: or application:didFinishLaunchingWithOptions: methods of your UIApplicationDelegate class.
You can read more about it in the Local and Push Notification Programming Guide
If you want to change the icon badge automatically use the following code.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
application.applicationIconBadgeNumber = 0;
NSLog(#"userInfo %#",userInfo);
for (id key in userInfo) {
NSLog(#"key: %#, value: %#", key, [userInfo objectForKey:key]);
}
[application setApplicationIconBadgeNumber:[[[userInfo objectForKey:#"aps"] objectForKey:#"badge"] intValue]];
NSLog(#"Badge %d",[[[userInfo objectForKey:#"aps"] objectForKey:#"badge"] intValue]);
}
We also need to change the php file. So we can get the change the icon badge automatically
// Create the payload body
$body['aps'] = array(
'alert' => $message,
'sound' => 'default',
'id' => '135',
'badge' => 8
);