iphone gps cllocation and making variables globally accessible - iphone

I'm pretty new to iPhone development and have been trying to work out how to include GPS information into an app I'm working on.
I've gone through the HelloThere tutorial, which is a great start
http://www.mobileorchard.com/hello-there-a-corelocation-tutorial/
And had no problems getting this to run on my iPhone. I then took the example and have since been trying to incorporate the GPS info into a much larger and more complicated app. The larger application has an existing function which will send a post request to the server, and I'd like to simply provide the location data, specifically the coordinate.latitude and coordinate.longitude to this function, if possible without altering it.
This is pretty trivial in the other languages I've worked with but it's turned out to be quite challenging in objective C.
Basically, as per the tutorial I have gotten to the point where I'm logging the location info,
//GPS stuff
- (void)locationUpdate:(CLLocation *)location {
//locationLabel.text = [location description];
locationString = [location description];
locationLabel.text = locationString;
locLat = [NSString stringWithFormat:#"%lf", location.coordinate.latitude];
locLong = [NSString stringWithFormat:#"%lf", location.coordinate.longitude];
}
but I can't figure out how I can then make the locLat and locLong variables available to other parts of the application. Pretty lame but I'm still a bit lost with objective C.

There are many ways to do this. The quick and dirty way (and some will frown upon it) is to just declare those as globals in this file and use extern to access them from other files.
Better is to make those #properties of the class, and provide a getter so you can access those from another class or part of the app. That does assume that this class will be available for other classes to access later on.
You also can use delegate to get information. And...
Thinking a bit more, I would probably store data like this someplace else, and will use this routine to update the value in that location (by using a setter of that class), so this method here would just get the location and then store it elsewhere.
You might want to read Scott Knaster's book on Objective C and Mac development for a primer on Obj C.

Here's how I recommend doing it:
Store lat/long in a dictionary and fire them off as strings bundled in a notification. Setup an observer in the application delegate and have the callback function store the lat/long in class properties of the application delegate and/or store them in the application defaults.
In your class where you acquire the coordinates:
- (void)locationUpdate:(CLLocation *)location {
NSString *locationString, *locLat, *locLong;
locationString = [location description];
locLat = [NSString stringWithFormat:#"%lf", location.coordinate.latitude];
locLong = [NSString stringWithFormat:#"%lf", location.coordinate.longitude];
NSDictionary *locationDictionary = [NSDictionary dictionaryWithObjectsAndKeys:locationString, #"description",
locLat, #"latitude", locLong, #"longitude", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"updateSearchLocation"
object:self userInfo:locationDictionary];
}
In your application delegate class:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Listen for search coordinates broadcast
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(setCoordinates:)
name:#"updateSearchLocation" object:nil];
}
- (void)setCoordinates:(id)sender {
self.latitude = [[sender userInfo] objectForKey:#"latitude"];
self.longitude = [[sender userInfo] objectForKey:#"longitude"];
NSLog(#"location = %#", [[sender userInfo] objectForKey:#"description"]);
}
Dont forget to setup the class properties in the application delegate header file as NSString. You can then access the coordinates by calling directly from the application delegate:
YourAppDelegateClassName *appDelegate = [[UIApplication sharedApplication] delegate];
NSLog(#"lat = %#, long = %#", appDelegate.latitude, appDelegate.longitude);
Or you can access them anywhere from the user defaults:
[[NSUserDefaults standardUserDefaults] objectForKey:#"latitude"];
[[NSUserDefaults standardUserDefaults] objectForKey:#"longitude"];
I hope that helps.

Related

How to access the Device token from didRegisterForRemoteNotificationsWithDeviceToken to my ViewDidLoad method

I know this is very basic but I need to pull my device token and Store it in a String and put(display) it on a label on my ViewDidLoad method. What might be the posible solution? Im new on iOS development. Should I use Global variable? or any Posible Solution?
This is my code.
AppDelegate.m
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSString *str = [NSString stringWithFormat:#"Device Token=%#",deviceToken];
NSLog(#"%#", str);
//get the string and display it on the ViewDidLoad method.
}
Please help me. How to use global variable to access that string?
on my ViewController.m class
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *getToken = #"Token from AppDelegate";
}
Something like that.
You can store the token into user defaults:
[[NSUserDefaults standardUserDefaults] setValue:str forKey:#"DEVICE_TOKEN"];
And then in your viewDidLoad:
NSString *getToken = [[NSUserDefaults standardUserDefaults] stringForKey:#"DEVICE_TOKEN"];
But there are other approaches: global variable, persistence layer, NSNotification, and so on.
EDIT:
Here you have info about how to register the standard user defaults.
EDIT2:
In order to obtain the token string, I do the following
NSString *deviceTokenString = [deviceToken description];
deviceTokenString = [deviceTokenString stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"<>"]];
deviceTokenString = [deviceTokenString stringByReplacingOccurrencesOfString:#" " withString:#""];
Then, in deviceTokenString you end having the full value without whitespaces or any other character. Just ready to trigger push notifications.
I usually create a category on UIApplication (or a UIApplication subclass) for storing the current push token.
#interface MyApplication : UIApplication
#property (nonatomic, copy) NSData *pushToken;
#end
As #amb mentioned, there are a lot of different ways to go about it.

Internal memory in iOS simulator

I just finished the second tutorial for iOS development. When I add a new sighting, it works fine and turns up in the list of sightings. However, when I close the iOS simulator and reopen it, only the first entry is there (it is added manually). Should there be internal memory in the iOS simulator even after it is closed and reopened?
There seems to be some memory as if I add my contact info to the contacts, it is still there when I reopen it.
If so, how do I make sure that my array in the DataController file is similarly stored on the simulator/phone so it doesn't clear itself every time I reopen the simulator?
Thanks
You need to use persistent storage if you want to save data between sessions. Options include:
You can use plist files. For example if you have NSArray *array, you can save that to a plist using writeToFile:
[array writeToFile:filename atomically:YES];
You can then read this array with:
NSArray *array = [NSArray arrayWithContentsOfFile:filename];
This technique only works with standard NSString, NSNumber, etc., objects, not custom objects like BirdSighting, though.
For custom objects like BirdSighting, you could use NSKeyedArchiver and NSKeyedUnarchiver. By the way, these are not only generally useful classes for saving data for small data sets like this, but given that it features prominently in the new iOS 6 state preservation features, it's worth familiarizing yourself with this pattern.
You can use NSUserDefaults. This is really better suited for app settings and defaults, but theoretically could be used for saving data, too.
You can use CoreData. This is the preferred iOS technology for object persistence. It's a powerful and well engineered framework (though a tad complicated) and well suited if you're dealing with more significant amounts of data.
You can use SQLite, too. See this Ray Wenderlich article on using SQLite. And once you start using SQLite, you can consider using FMDB to simplify your coding effort.
If you wanted, for example, to use the second approach, NSKeyedArchiver and NSKeyedUnarchiver, the first thing is that you might want to do is make BirdSighting conform to NSCoding, by altering the #interface declaration in BirdSighting.h to say:
#interface BirdSighting : NSObject <NSCoding>
Second, you have to write the two NSCoding methods, initWithCoder and encodeWithCoder, in BirdSighting.m that define what properties can to be loaded/saved for this object:
- (NSArray *)keysForEncoding;
{
return #[#"name", #"location", #"date"];
}
- (id) initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
for (NSString *key in [self keysForEncoding])
{
[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
for (NSString *key in self.keysForEncoding)
{
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
}
Your BirdSighting can now be loaded and saved with NSKeyedUnarchiver and NSKeyedArchiver, respectively.
So, focusing on the loading of the sightings, you have to (a) tell BirdSightingDataController.m what file to look for; and (b) instruct it to read that file during initialization:
- (NSString *)filename
{
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
return [docsPath stringByAppendingPathComponent:#"BirdSightings"];
}
- (void)initializeDefaultDataList
{
NSString *filename = [self filename];
self.masterBirdSightingList = nil;
if ([[NSFileManager defaultManager] fileExistsAtPath:filename])
{
self.masterBirdSightingList = [NSKeyedUnarchiver unarchiveObjectWithFile:filename];
}
if (!self.masterBirdSightingList)
{
NSMutableArray *sightingList = [[NSMutableArray alloc] init];
self.masterBirdSightingList = sightingList;
BirdSighting *sighting;
NSDate *today = [NSDate date];
sighting = [[BirdSighting alloc] initWithName:#"Pigeon" location:#"Everywhere" date:today];
[self addBirdSightingWithSighting:sighting];
}
}
The BirdSightingDataController.m can also define a method to save the data:
- (BOOL)save
{
return [NSKeyedArchiver archiveRootObject:self.masterBirdSightingList toFile:[self filename]];
}
You can now, for example, call this save method whenever you add a sighting, e.g.:
- (void)addBirdSightingWithSighting:(BirdSighting *)sighting
{
[self.masterBirdSightingList addObject:sighting];
[self save];
}
Personally, rather than saving it every time a user does any change in the app, I might rather just have my app delegate save it when the app goes into background or terminates (but that requires further changes, so I won't go into that now).
But hopefully this code illustrates how you can use NSKeyArchiver and NSKeyUnarchiver to save and load data. And clearly, for more complicated scenarios, I would generally encourage you to consider Core Data. But for small data sets, like this, this archiver pattern can be useful (and as I said, is worth being familiar with because the basic technology is also used in iOS 6 app state restoration).

Can't get data from Google Places

I am trying to get the google places API to work on my iPhone project. Now, I had it working about an hour ago, but I can't seem to figure out what I did to make it stop working. Any help would be appreciated.
Here is what I have so far:
- (NSString *)searchString {
// this mutable string allows me to dynamically create the search string
// we start with the static part of the api search URL
NSMutableString *result = [NSMutableString stringWithString:#"https://maps.googleapis.com/maps/api/place/search/json?location="];
// since I need to get the user's location, I need to create a location manager
CLLocationManager *locationManager = [[CLLocationManager alloc] init];
// we need to now update the current location,
// otherwise there will be no coordinates
[locationManager startUpdatingLocation];
// now that it's updated, we stop it because I
// am not tracking anything
[locationManager stopUpdatingLocation];
// this appends the lattitude/longitude, as double values, into the URL
[result appendFormat:#"%g,%g", [[locationManager location] coordinate].latitude, [[locationManager location] coordinate].longitude];
// release the location manager for memory management
[locationManager release];
// if a filter is present, add the keyword item to try to filter
// the results
if([[self filterString] length] > 0) {
[result appendFormat:#"&keyword=%#", filterString];
}
// add the rest of the validated URL now
//[result appendString:#"&types=food|meal_delivery|meal_takeaway|restaurant&rankby=distance&sensor=true&key=AIzaSyBmO_f6h4_Q0xArw6tdxUF7TH7rZpaiFfQ"];
[result appendString:#"&types=food&rankby=distance&sensor=true&key=mykey"];
// log the result for testing
NSLog(#"Completed Search String: %#", result);
return result;
}
Now, when I look at my log, copy the 'completed search string' into Safari, it brings up the results that I need.
But if I use the following code, the app hangs:
- (void)performSearch {
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[self searchString]]]; // hangs on this line!
NSDictionary *jsonDictionary = [data objectFromJSONData];
NSArray *resultsArray = [jsonDictionary objectForKey:#"results"];
currentList = [ARGooglePlace placesWithArray:resultsArray];
[self.tableView reloadData];
}
I think I should mention that I am using the JSONKit to do the JSON parsing. Also, the ARGooglePlace is a custom class that isn't relevant right now (it doesn't even get there...)
Thanks for any help that you can provide.
Pull the location manager and the lat/long out of the searchString method... just put it in the performSearch method. And instead, pass the lat/long as received from location manager into searchString.
It sounds like a timing issue with your location manager. It could have been working earlier b/c location manager had previously cached location data... and was able to grab the correct coords.
2+ Possible scenarios:
1) problem with location manager not updating it's coords and just hanging there
2) google website being the culprit (maybe loading too much data??)
Pulling location manager out of the searchString method will help isolate cause... and one can just pass lat/long values directly to test google website as well.

MPNowPlayingInfoCenter defaultCenter will not update or retrieve information

I'm working to update the MPNowPlayingInfoCenter and having a bit of trouble. I've tried quite a bit to the point where I'm at a loss. The following is my code:
self.audioPlayer.allowsAirPlay = NO;
Class playingInfoCenter = NSClassFromString(#"MPNowPlayingInfoCenter");
if (playingInfoCenter) {
NSMutableDictionary *songInfo = [[NSMutableDictionary alloc] init];
MPMediaItemArtwork *albumArt = [[MPMediaItemArtwork alloc] initWithImage:[UIImage imageNamed:#"series_placeholder"]];
[songInfo setObject:thePodcast.title forKey:MPMediaItemPropertyTitle];
[songInfo setObject:thePodcast.author forKey:MPMediaItemPropertyArtist];
[songInfo setObject:#"NCC" forKey:MPMediaItemPropertyAlbumTitle];
[songInfo setObject:albumArt forKey:MPMediaItemPropertyArtwork];
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:songInfo];
}
This isn't working, I've also tried:
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:nil];
In an attempt to get it to remove the existing information from the iPod app (or whatever may have info there). In addition, just to see if I could find out the problem, I've tried retrieving the current information on app launch:
NSDictionary *info = [[MPNowPlayingInfoCenter defaultCenter] nowPlayingInfo];
NSString *title = [info valueForKey:MPMediaItemPropertyTitle];
NSString *author = [info valueForKey:MPMediaItemPropertyArtist];
NSLog(#"Currently playing: %# // %#", title, author);
and I get Currently playing: (null) // (null)
I've researched this quite a bit and the following articles explain it pretty thoroughly, however, I am still unable to get this working properly. Am I missing something? Would there be anything interfering with this? Is this a service something my app needs to register to access (didn't see this in any docs)?
Apple's Docs
Change lock screen background audio controls
Now playing info ignored
I finally figured out the problem, I was not prompting my app to receive remote control events, simply adding this line fixed the problem:
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
I use the code below and it always works. I'm also using MPMoviePlayer like you. Have you checked whether NSClassFromString(#"MPNowPlayingInfoCenter") ever actually returns YES? Have you set you app play audio in background key in your plist?
- (void) loadMPInformation
{
NSDictionary *mpInfo;
if([savedTrack.belongingAlbum.hasAlbumArt boolValue] == NO){
mpInfo = [NSDictionary dictionaryWithObjectsAndKeys:savedTrack.belongingAlbum.album, MPMediaItemPropertyAlbumTitle,
savedTrack.belongingArtist.artist, MPMediaItemPropertyArtist, savedTrack.name, MPMediaItemPropertyTitle, nil];
} else {
UIImage *artImage = [UIImage imageWithData:savedTrack.belongingAlbum.art];
MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:artImage];
mpInfo = [NSDictionary dictionaryWithObjectsAndKeys:savedTrack.belongingAlbum.album, MPMediaItemPropertyAlbumTitle,
savedTrack.belongingArtist.artist, MPMediaItemPropertyArtist, savedTrack.name, MPMediaItemPropertyTitle, artwork, MPMediaItemPropertyArtwork, nil];
}
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = mpInfo;
}

NSUserDefaults not present on first run on simulator

I've got some settings saved in my Settings.bundle, and I try to use them in application:didFinishLaunchingWithOptions, but on a first run on the simulator accessing objects by key always returns nil (or 0 in the case of ints). Once I go to the settings screen and then exit, they work fine for every run thereafter.
What's going on? Isn't the point of using default values in the Settings.bundle to be able to use them without requiring the user to enter them first?
If I got your question right, in your app delegate's - (void)applicationDidFinishLaunching:(UIApplication *)application, set the default values for your settings by calling
registerDefaults:dictionaryWithYourDefaultValues
on [NSUserDefaults standardUserDefaults]
- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:3], #"SomeSettingKey",
#"Some string value", #"SomeOtherSettingKey",
nil];
[ud registerDefaults:dict];
}
These values will only by used if those settings haven't been set or changed by previous executions of your application.
As coneybeare said "You should detect if it is the first load, then store all your defaults initially."
On applicationDidFinishLaunching try to set default value in your preference.
Here is the sample:
NSUserDefaults *defaults =[NSUserDefaults standardUserDefaults];
if([defaults objectForKey:#"YOUR_KEY"] == nil)
{
[defaults setValue:#"KEY_VALUE" forKey:#"YOUR_KEY"];
}
When application will run second time it will come with KEY_VALUE for YOUR_KEY.
Thanks,
Jim.
Isn't the point of using default
values in the Settings.bundle to be
able to use them without requiring the
user to enter them first?
No. The point of the settings bundle is to give the user a place to edit all 3rd Party app settings in a convenient place. Whether or not this centralization is really a good idea is a User Experience issue that is off topic.
To answer your question, you should detect if it is the first load, then store all your defaults initially.
And while we are on the subject, I would also check out In App Settings Kit as it provides your app with a simple way to display your app settings in both places (in-app and Settings.app) with minimal code.
The values in the Settings.bundle are intended for the Settings app to able to fill in default values for your app. They are not used by your own app.
But you can set defaults yourself with the registerDefaults: method of NSUserDefaults. This will not actually set them on disk but just give "defaults for the defaults": they are used when no value has been set by the user yet.
Setting registerDefaults: must be done before any use of the default values. The "applicationDidFinishLaunching:" method that others suggested for this, is too late in most cases. By the time "applicationDidFinishLaunching:" is called, your views have already been loaded from the nib files, and their "viewDidLoad:" methods have been called. And they may typically read user defaults.
To guarantee that the defaults are set before first use, I use the following utility class, which loads the values from the Root.plist file and sets them with "registerDefaults:". You use this class to read user defaults instead of "[NSUserDefaults standardUserDefaults]". Use "[Settings get]" instead.
As a bonus, it also contains a registration method for user default change notifications, because I always forget how that is done.
#import "Settings.h"
#implementation Settings
static bool initialized = NO;
+ (void) setDefaults
{
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
NSString *settingsBundlePath = [bundlePath stringByAppendingPathComponent:#"Settings.bundle"];
NSBundle *settingsBundle = [NSBundle bundleWithPath:settingsBundlePath];
NSString *settingsPath = [settingsBundle pathForResource:#"Root" ofType:#"plist"];
NSDictionary *settingsDict = [NSDictionary dictionaryWithContentsOfFile:settingsPath];
NSArray *prefSpecifierArray = [settingsDict objectForKey:#"PreferenceSpecifiers"];
NSMutableDictionary *appDefaults = [[NSMutableDictionary alloc] init];
for (NSDictionary *prefItem in prefSpecifierArray)
{
NSString *key = [prefItem objectForKey:#"Key"];
if (key != nil) {
id defaultValue = [prefItem objectForKey:#"DefaultValue"];
[appDefaults setObject:defaultValue forKey:key];
}
}
// set them in the standard user defaults
[[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults];
if (![[NSUserDefaults standardUserDefaults] synchronize]) {
NSLog(#"Settings setDefaults: Unsuccessful in writing the default settings");
}
}
+ (NSUserDefaults *)get
{
if (!initialized) {
[Settings setDefaults];
initialized = YES;
}
return [NSUserDefaults standardUserDefaults];
}
+ (void) registerForChange:(id)observer selector:(SEL)method
{
[[NSNotificationCenter defaultCenter] addObserver:observer selector:method name:NSUserDefaultsDidChangeNotification object:nil];
}
+ (void) unregisterForChange:(id)observer
{
[[NSNotificationCenter defaultCenter] removeObserver:observer name:NSUserDefaultsDidChangeNotification object:nil];
}