Best Way to Handle Local Notifications - ios

I have the following problem:
I have 2 calendars which both need to create local notifications (which will fire 5 min before each event). In the settings the user has the ability to turn notifications on or off for either calendar. If the user was originally using the notifications for both calendars and now wants to only use notifications for one calendar, how can I delete only the notifications in one calendar?
I think I have 3 options:
Running [[UIApplication sharedApplication] cancelAllLocalNotifications]; and then add back all the ones in the other calendar (this will probably be harder than it sounds).
Storing arrays of created notifications in something like the user defaults and then looping through the arrays calling: [[UIApplication sharedApplication] cancelLocalNotification:notification];
Subclassing UILocalNotification and adding some field which will allow me to sort the notifications. Then maybe I could call [[UIApplication sharedApplication] scheduledLocalNotifications] and loop through that checking that field and deleting those which are necessary.
Is there a standard way of doing this? I think the third is probably the easiest, but I'm not sure if it would work.

UILocalNotification has a standard userInfo property that is a NSDictionary of arbitrary values as long as the keys are valid property-list types. If you subclass UILocalNotification, you must use that dictionary as the backing store for additional properties or fields you wish to persist. And to be able to use your subclass, you will need an initialization method that will copy the properties from the base class to your subclass.
#define kNoficationCalendarName NSStringFromSelector(#selector(calendarName))
#interface XXLocalNotification : UILocalNotification
#property (nonatomic, strong) NSString * calendarName;
- (instancetype)initWithLocalNotification:(UILocalNotification *)notification;
#end
#implementation XXLocalNotification
- (instancetype)initWithLocalNotification:(UILocalNotification *)notification
{
self = [super init];
if (self)
{
//Copy properties
self.alertAction = notification.alertAction;
self.alertBody = notification.alertBody;
self.alertLaunchImage = notification.alertLaunchImage;
self.applicationIconBadgeNumber = notification.applicationIconBadgeNumber;
self.fireDate = notification.fireDate;
self.hasAction = notification.hasAction;
self.repeatCalendar = notification.repeatCalendar;
self.repeatInterval = notification.repeatInterval;
self.soundName = notification.soundName;
self.timeZone = notification.timeZone;
self.userInfo = notification.userInfo;
}
return self;
}
-(void)setCalendarName:(NSString *)calendarName
{
NSMutableDictionary * userInfo = [[self userInfo] mutableCopy];
[userInfo setValue:calendarName
forKey:kNoficationCalendarName];
}
- (NSString *)calendarName
{
return [[self userInfo] valueForKey:kNoficationCalendarName];
}
#end

Related

Urban Airship: Generate channel ID during the first run of the iOS app

I have registered with Urban Airship, obtained the app key, secret and master secret. I have integrated their library as well.
-(void)setupUrbanAirship
{
UAConfig *config = [UAConfig defaultConfig];
config.detectProvisioningMode = YES;
config.productionLogLevel=NO;
config.inProduction = NO;
config.developmentAppKey = #"XXXXXXXXXXXXXXXXXXX";
config.developmentAppSecret = #"XXXXXXXXXXXXXXXXX";
[UAirship takeOff:config];
[UAirship push].userPushNotificationsEnabled = YES;
[UAirship push].userNotificationTypes = (UIUserNotificationTypeAlert |
UIUserNotificationTypeBadge |
UIUserNotificationTypeSound);
}
I am calling this in my code:
NSString *channelID = [UAirship push].channelID;
NSLog(#"channelID %#",channelID);
During the first run of the app the channelID is always null.
I am able to receive the channelID during the second run but not during the first run. Could anyway suggest a way for the app to obtain the channelID during the first run itself. Thanks in advance.
Edited:
According to ralepinski's suggestion I am adding the '[UAirship push].registrationDelegate = self.registrationDelegate' line of code.
In ViewController.h :
#interface ViewController : UIViewController
{
IBOutlet UIButton *selectButton;
NSTimer *channelIDDetectionTimer;
}
#property (nonatomic, weak, nullable) id<UARegistrationDelegate> registrationDelegate;
In ViewController.m I am also using these lines of code:
-(void)initRegistration
{
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
NSString *channelID = [UAirship push].channelID;
NSLog(#"channelID %#",channelID);
if (channelID!=nil)
{
[delegate saveChannelID];
if (channelIDDetectionTimer!=nil)
{
[channelIDDetectionTimer invalidate];
channelIDDetectionTimer = nil;
}
}
else
{
[UAirship push].registrationDelegate = self.registrationDelegate;
//[[UAPush alloc]updateRegistration];
// Registering for only UIRemoteNotificationTypeNone will not result in a
// device token registration call. Instead update chanel registration directly.
}
}
-(void)viewWillAppear:(BOOL)animated
{
channelIDDetectionTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(initRegistration) userInfo:nil repeats:YES];
}
I have enabled remote-notifications in the capabilities section of the app target. Still I get the ChannelID as null during the app's first run. Let me know. Thanks.
The channel creation relies on a network request. It wont immediately be available, but you can listen for when its available using UARegistrationDelegate. You can assign the delegate on the UAPush instance here:
[UAirship push].registrationDelegate = self.registrationDelegate;
Sometimes the channel registration is delayed to the next app foreground if the SDK is unable to generate a deviceToken. If you enable remote-notifications in the capabilities section of your application the device token will be generated immediately, and the channel created during the first run.

Web call in DidUpdateLocations while app is terminated

As my user changes location, I need to compare his location with an array of locations I pull from the web. However, in my appDelegate, I'm not sure where exactly to place my code as I'm not sure what methods are called or not called when the app is terminated, but the CLLocationManager still works.
Specifically, I need to input this code where it will actually be called when the app is still terminated:
// alloc and init the various (Mutable)Array properties
self.locations = [[NSMutableArray alloc] init];
// Create new HomeModel object and assign it to _homeModel variable
_homeModel = [[DealsModel alloc] init];
// Set this view controller object as the delegate for the home model object
_homeModel.delegate = self;
// Call the download items method of the home model object
[_homeModel downloadItems];
The _homeModel will then call this method:
-(void)itemsDownloaded:(NSArray *)items
{
// This delegate method will get called when the items are finished downloading
// Set the downloaded items to the array
_locations = [items copy];
}
Which I will further edit to compare the user's location to the array of locations.
The thing is, this array of locations only changes once a week. Does the app really have to pull it from the web every time the user's location changes? Or is there a way to cache this and only pull it when self.locations has been deallocated?
This is what I have now, but I feel there must be a better way:
#interface AppDelegate () <CLLocationManagerDelegate>
{
DealsModel *_homeModel;
}
#property BOOL didRunBefore;
#property CLLocationManager *locationManager;
#property NSMutableArray *deals;
#end
#implementation AppDelegate
-(void)itemsDownloaded:(NSArray *)items
{
// This delegate method will get called when the items are finished downloading
// Set the downloaded items to the array
_deals = [items copy];
[self compareSponsorLocations:_deals toUserLocation:[self.locationManager location]];
}
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations {
if (!self.deals) {
// alloc and init the various (Mutable)Array properties
self.deals = [[NSMutableArray alloc] init];
// Create new HomeModel object and assign it to _homeModel variable
_homeModel = [[DealsModel alloc] init];
// Set this view controller object as the delegate for the home model object
_homeModel.delegate = self;
// Call the download items method of the home model object
[_homeModel downloadItems];
} else {
[self compareSponsorLocations:self.deals toUserLocation:[locations lastObject]];
}
}
- (void) compareSponsorLocations: (NSArray *) array toUserLocation: (CLLocation *) location
{
for (Deal *deal in array) {
NSLog(#"%#", deal.name);
}
NSLog(#"%#", location.description);
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
To handle location updates when you app is in background or even terminated, you can use "Significant Location Changes". It will trigger application to start in background mode when location has been changed significantly. Then you can start a background task to perform the operations on your need.
Documentation:
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/LocationAwarenessPG/CoreLocation/CoreLocation.html#//apple_ref/doc/uid/TP40009497-CH2-SW8
Easy way to simulate it and debug:
XCode / iOS simulator: Trigger significant location change manually

Pass BOOL value

I am working on an app and I got stuck at the point where I can't seem to retrieve the value of a BOOL set in a class.
I spent too much time already on it, been through all the questions I found that seem to cover the matter.
The bad thing here is that I get something, but not what I need (I get a 0, which means, I guess, that the value wasn't retrieved correctly as it should be 1).
The things I tried are :
pass a pointer to my first class and access to my BOOL like this:
//in some method
self.pointerFirstClass.myBOOL;
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
by declaring it (talking of the pointer) as a property in my second class (and importing the h. file from my first class, where my BOOL is declared as property too):
#property FirstClass *pointerFirstClass;
But I got 0 using this.
The other shot I gave was add my BOOL in the first class and create an instance of the class in my second class
//in some method
FirstClass *firstClass = [[FirstClass alloc] init];
if (firstClass.myBOOL){
//Do something
}
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
But I got 0 too.
As Booleans are primitive types, like in C, I get a bit confused since I am new to object-oriented programming, I don't know how I could like create a getter for this, for example.
I also tried to do a - (BOOL)getBOOLValue method in my first class, and call this method in my second class and assign it to a BOOL in that second class.
But the result wasn't better.
Am I missing something?
Is there a way to get my value that I didn't think of or didn't know about yet?
I am running low on thoughts on how to get around this, it shouldn't be that hard IMO so I hope it is something simple that I just left aside.
EDIT :
Some actual code. I am working between 2 files called AppDelegate (yes, the actual one) and WelcomeViewController (so a VC).
AppDelegate.h
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
BOOL inRegion; //thought of this making my BOOL as a property of AppDelegate
}
#property (strong, nonatomic) UIWindow *window;
#property BOOL inRegion; //Declaring my BOOL here to make it accessible for another class
- (BOOL)getBOOLValue; //An attempt to pass my BOOL value
AppDelegate.m
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
if (state == CLRegionStateInside)
{
self.inRegion = YES; //Set my BOOL to TRUE
}
else if (state == CLRegionStateOutside)
{
self.inRegion = NO; //Else set it to False
}
- (BOOL)getBOOLValue
{
return inRegion; //Tried to create a custome "getter"
}
WelcomeViewControler.m (I changed nothing in the .h file)
I said I tried many things, right now, this is the last version of my code.
//Simply trying to do a Segue on a condition...
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init];
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
else
{
//Do Nothing
}
}
As said, I want to retrieve the BOOL value of the AppDelegate.
Thank you.
This code doesn't make sense:
self.pointerFirstClass.myBOOL;
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
The first line doesn't do anything. You're not assigning anything to the property, and you're not doing anything with the value. Furthermore, the second line doesn't relate to the first line in any way that we can see from the code you've provided. Try this instead:
self.pointerFirstClass = [[FirstClass alloc] init];
self.pointerFirstClass.myBOOL = YES;
NSLog(#"myBOOL = %d", self.pointerFirstClass.myBOOL);
In other words, you need to be sure that self.pointerFirstClass points to a valid object. And then you need to make sure that you've assigned the value you want to the myBOOL property of that object.
Update: This looks like a case where you're talking to the wrong object. Look at this:
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init];
This is surely not what you really want. The application object is a single object -- a real singleton, in fact, meaning that there is and can be only one application object. That object has a delegate object, and that's a specific instance of your AppDelegate class. In this code, though, you're creating a new instance of AppDelegate, one that's different from the one that the application is using. Any changes that are made to the actual application delegate in response to messages from the application will not be reflected in the new object that you've created.
What I think you want is to get the actual application delegate object, and you can do that using:
[[UIApplication sharedApplication] delegate];
So, change your code to look like this:
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];;
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
// note: you don't need an else clause if it doesn't do anything
}
That way, you'll be talking to the same object that the app uses, which is the one that has the inRegion property set in response to the location manager call.
UPDATE - Now we can see your code the problem is obvious, you are trying to access the appDelegate by creating a new one...
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init];
Instead you should be doing this....
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate]
--
Not sure if you are posting your actual code? but the first example you give...
self.pointerFirstClass.myBOOL;
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
Shouldn't the second line be
NSLog(#"%d", self.pointerFirstClass.myBOOL);
Also this property...
#property FirstClass *pointerFirstClass;
Won't retain it once you've set it, it needs to be
#property (nonatomic,strong) FirstClass *pointerFirstClass;
In the second example...
FirstClass *firstClass = [[FirstClass alloc] init];
if (firstClass.myBOOL){
//Do something
}
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
You allocate and initialise a new FirstClass object and then check the property straight away, if you are not setting this to YES in the init then it will be false
Like I say, maybe you're not posting your actual code?
I guess what you want is initializing myBOOL to 1.
If so, you need do something as following
#implement FirstClass
- (id)init
{
self = [super init];
if(self) {
_myBOOL = 1;
}
return self;
}
// Other methods
#end
EDIT:
The comments is why you get 0.
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init]; // this is the problem.
// you create a new appdelegate,
// and never call locationManager:didDetermineState:forRegion:
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
else
{
//Do Nothing
}
}
rewrite your code as following:
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
else
{
//Do Nothing
}
}

Prevent iOS in-call status bar from pushing Phonegap UIWebView offscreen

My issue is this: whenever an iPhone user is in call, or is using his or her phone as a hotspot, the iOS 7 status bar is enlarged, thus pushing my Phonegap application's UIWebView off the bottom of the screen. The enlarged status bar is termed the "in-call status bar". See below image:
Stack Overflow answers I have tried to remedy this:
Iphone- How to resize view when call status bar is toggled?
How In-Call status bar impacts UIViewController's view size ? (and how to handle it properly)
Additionally, there does not seem to be any sort of event fired by Phonegap that informs me of the status bar's change. Listening to the Phonegap "pause" event is useless, as 1) it's known to have quirks in iOS and 2) it doesn't really cover the hotspot case.
My Objective-C skills are very minimal, and I only resort to asking this sort of question after putting in the requisite 4+ hours Googling, Stack Overflowing, wailing, etc...
Gods of Stack Overflow, render unto me thine bounteous nerd fury.
Came up with the following solution based on Jef's suggestions. What you'll want to do is the following:
Observe the native didChangeStatusBarFrame delegate
Get size information about the statusbar via native statusBarFrame
Expose information to your webview by triggering an event that passes it
I have setup a Github repo with all the code you find in this answer.
Setup notification in AppDelegate
// Appdelegate.m
- (void)application:(UIApplication *)application didChangeStatusBarFrame:(CGRect)oldStatusBarFrame
{
NSMutableDictionary *statusBarChangeInfo = [[NSMutableDictionary alloc] init];
[statusBarChangeInfo setObject:#"statusbarchange"
forKey:#"frame"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"statusbarchange"
object:self
userInfo:statusBarChangeInfo];
}
Make statusBarChange selector available
// MainViewController.h
#protocol StatusBarChange <NSObject>
-(void)onStatusbarChange:(NSNotification*)notification;
#end
Setup the listener. This gets the origin and size dictionaries from statusBarFrame whenever it changes and fires an event in the webview passing along this data.
// MainViewController.m
- (void)onStatusbarChange:(NSNotification*)notification
{
// Native code for
NSMutableDictionary *eventInfo = [self getStatusBarInfo];
[self notifiy:notification.name withInfo:eventInfo];
}
- (void)notifiy:(NSString*)event withInfo:(NSMutableDictionary*)info
{
NSString *json = [self toJSON:info];
NSString *cmd = [NSString stringWithFormat:#"cordova.fireWindowEvent('\%#\', %#)", event, json];
[self.webView stringByEvaluatingJavaScriptFromString:cmd];
}
- (NSMutableDictionary *)getStatusBarInfo
{
CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
NSMutableDictionary *statusBarInfo = [[NSMutableDictionary alloc] init];
NSMutableDictionary *size = [[NSMutableDictionary alloc] init];
NSMutableDictionary *origin = [[NSMutableDictionary alloc] init];
size[#"height"] = [NSNumber numberWithInteger:((int) statusBarFrame.size.height)];
size[#"width"] = [NSNumber numberWithInteger:((int) statusBarFrame.size.width)];
origin[#"x"] = [NSNumber numberWithInteger:((int) statusBarFrame.origin.x)];
origin[#"y"] = [NSNumber numberWithInteger:((int) statusBarFrame.origin.y)];
statusBarInfo[#"size"] = size;
statusBarInfo[#"origin"] = origin;
return statusBarInfo;
}
- (NSString *) toJSON:(NSDictionary *)dictionary {
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dictionary options:NSJSONWritingPrettyPrinted error:&error];
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
All this allows you to listen for window.statusbarchange event, e.g. like this:
// www/js/index.js
window.addEventListener('statusbarchange', function(e){
// Use e.size.height to adapt to the changing status bar
}, false)
I'd say that this always happens when you return from background, no?
In others words is it possible for the bar to enlarge without your app being at least briefly pushed to the background by incoming call etc?
If so surely you can query the status bar height in your delegates -(void)will(orDid)resume and adjust accordingly?
If it does happen without leaving the foreground that make a little more difficult, we'll need to work out which notifications to observe for, I know there's an audioSession interruption notif in the case of incoming calls, not sure about the hotspot thing but surely there is a notification for that too..
Edit ok here they are, choose one of these notifications to observe..
UIApplicationWillChangeStatusBarFrameNotification
UIApplicationDidChangeStatusBarFrameNotification
Or implement one of these callbacks in your delegate
-application:willChangeStatusBarFrame:
-application:didChangeStatusBarFrame:

Variables and Transferring Data between View Controllers

I know that there are tutorials everywhere, but I can't figure this out for some reason. I have a tab bar controller. Each tab links to a navigation controller, which is segued to a view controller. So, 2 main view controllers (StatusVC and TransactionsVC).
In StatusVC, I have a text field. In TransVC, I have a table view. A person adds a cell to the table. Math is done behind the scenes. The cell values are added together (numbers). This information is sent back to StatVC for calculations and displaying of the data. I've already got the math part down. My question: how do I transfer the data between view controllers, and better yet, how do I store this data so that it doesn't get deleted on quit (NSUserDefaults probably)?
This can be broken down I suppose, the transferring of data, the saving of data, and the displaying of data when the tab is pressed and view is shown.
I'm hoping this is making sense. Anyway, here's the code I've got. You're looking at TranVC. User enters data into the table with an alert view. You are looking at part of the Alert View delegate methods. This is when the user enters data into a cell (presses done). Look for key areas with the ******* comments.
StatusViewController *statVC = [[StatusViewController alloc]init]; //*******init
// Set the amount left in the budget
NSString *amountToSpend = statVC.amountLeftInBudget.text;
double budgetLabel = [amountToSpend doubleValue];
NSString *lastItem = [transactions objectAtIndex:0];
double lastLabel = [lastItem doubleValue];
double totalValue = budgetLabel - lastLabel;
NSString *amountToSpendTotal = [NSString stringWithFormat: #"%.2f", totalValue];
statVC.amountLeftInBudget.text = amountToSpendTotal; //*******set text (but not save), either way, this doesn't work
// Set the amount spent
NSString *sum = [transactions valueForKeyPath:#"#sum.self"];
double sumLabel = [sum doubleValue];
NSString *finalSum = [NSString stringWithFormat:#"%.2f", sumLabel];
//Set the amountSpent label
statVC.amountSpent.text = finalSum; //*******set text (but not save), either way, this doesn't work
// The maxed out budget section
if ([statVC.amountLeftInBudget.text isEqualToString: #"0.00"]) //*******set color (but not save), either way, this doesn't work
{
statVC.amountLeftInBudget.textColor = statVC.currencyLabel.textColor = [UIColor redColor];
} else if ([statVC.amountLeftInBudget.text compare:#"0.00"] == NSOrderedAscending)
{
statVC.amountLeftInBudget.textColor = statVC.currencyLabel.textColor = [UIColor redColor];
} else if ([statVC.amountLeftInBudget.text compare:#"0.00"] == NSOrderedDescending)
{
statVC.amountLeftInBudget.textColor = statVC.currencyLabel.textColor = [UIColor colorWithRed:23.0/255.0 green:143.0/255.0 blue:9.0/255.0 alpha:1.0];
}
if ([statVC.amountLeftInBudget.text compare:#"0.00"] == NSOrderedAscending)
{
// Create our Installation query
UIAlertView *exceed;
exceed = [[UIAlertView alloc]
initWithTitle: #"Budget Exceeded"
message: #"You have exceeded your budget amount"
delegate: self
cancelButtonTitle: #"Okay"
otherButtonTitles: nil];
[exceed show];
}
Any help with this would be amazing.
This is indeed a common question.
There are various solutions. The one I recommend is to use a data container singleton. Do a google search on the singleton design pattern in Objective C. You'll even find examples of it here on SO.
Create a singleton with properties for the values that you want to share. Then teach your singleton to save it's data. You can use user defaults, you can use NSCoding, you can extract the data to a dictionary and save it to a plist file in your documents directory, or various other schemes as well.
Like Duncan suggested, a Singleton pattern might be the best route to go. If you place the shared data into a model class, you can create a class method that can be used to acquire a singleton object.
MyModel.m
#implementation MyObject
- (id) init
{
return nil; // We force the use of a singleton. Probably bad practice?
}
// Private initializer used by the singleton; not included in the header file.
- (id)initAsSingleton {
self = [super init];
if (self) {
// Initialize your singleton instance here.
}
return self;
}
+ (MyModel *)sharedMyModel {
static MyModel *myModel = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
myModel = [[MyModel alloc] initAsSingleton];
});
return myModel;
}
MyModel.h
#interface MyModel : NSObject
+ (MyModel *)sharedMyModel; // Singleton instance.
#end
This does not protect against you using [[MyModel alloc] init];. It returns a nil object which is probably poor programming on my end, but it does force you to use the singleton object instead. To use in each one of your view controllers, you just use the following line to grab the singleton instance.
MyModel *model = [MyModel sharedMyModel];
Store the data into it, and return to your other view controller and grab the singleton again. You'll have all of your data.
After thinking about it, you could also force the default initializer to just return your singleton instance like:
- (id)init {
return [MyModel sharedMyModel];
}

Resources