How do I acquire data from a NSNotification object? - iphone

I am trying to send a location update with the new location as a notification object. When I do, I receive a "EXC_BAD_ACCESS" error when I try to access the data from the notification. If I execute "po location" I see the data, but it is unclear to me why I cannot acquire it. When setting the observer, I also tried assigning the object parameter to a member variable, but then locationUpdate is never called.
Here's my code (note that ARC is enabled):
// LocationController.h
#protocol LocationDelegateProtocol
#required
- (void)locationUpdate:(CLLocation *)location;
#end
#interface LocationController : NSObject <CLLocationManagerDelegate> {
CLLocationManager *locationManager;
id delegate;
}
#property(nonatomic, retain) CLLocationManager *locationManager;
#property(nonatomic, strong) id delegate;
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation;
+ (LocationController *)sharedInstance; // this class is a singleton
#end
// LocationController.m
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
[Notification locationChanged:newLocation];
}
// Notification.h
#interface Notification : NSObject
+ (void)locationChanged:(CLLocation *)newLocation;
#end
extern NSString *const kLocationChanged;
// Notification.m
NSString *const kLocationChanged = #"NewLocation";
[[NSNotificationCenter defaultCenter] postNotificationName:kLocationChanged object:newLocation];
// ViewController.h
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, LocationDelegateProtocol> {
...
}
...
- (void)locationUpdate:(CLLocation *)location;
#end
// ViewController.m
- (void)setupNotifications {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(locationUpdate:) name:kLocationChanged object:nil];
// I've tried setting object to a member var "CLLocation *objectFromNotification", but then locationUpdate() is never called.
}
- (void)locationUpdate:(NSNotification *)notification {
CLLocation *location = (CLLocation *) [notification object];
// program receives signal "EXC_BAD_ACCESS" when executing NSLog below. I can see data inside location when I execute "po location".
NSLog(#"latitude = %#, longitude = %#",location.coordinate.latitude, location.coordinate.longitude);

Change the format specifier in your NSLog from %# to %f. You are trying to access float value as object!

NSNotifications have a dictionary with them called userInfo where you can put information you want to be sent with the notification.
I am going to fix your code kinda going backwards, so bear with me. You really haven't used the NSNotification class as it is typically (or intended to be) used.
To fix this situation, we have to do a bunch of things. The object value of an NSNotification post is the object that is posting the NSNotification, not the object you want to pass with it.
Add the CLLocation object to a dictionary, and pass it in as the userInfo dictionary. There is also no reason for this custom notification class stuff you have. So you can get rid of the Notification.h and Notification.m
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSString *const kLocationChanged = #"NewLocation";
NSDictionary *locationDict = [NSDictionary dictionaryWithObject:newLocation forKey:#"Location"];
[[NSNotificationCenter defaultCenter] postNotificationName:kLocationChanged object:nil userInfo:locationDict];
}
So now we are posting the location information with the notification. Next, handle it when you get the notification.
- (void)locationUpdate:(NSNotification *)notification {
CLLocation *location = [[notification userInfo] valueForKey:#"Location"];
NSLog(#"latitude = %f, longitude = %f",location.coordinate.latitude, location.coordinate.longitude);
}
Also, change your view controller header to the following:
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, LocationDelegateProtocol> {
...
}
...
- (void)locationUpdate:(NSNotification *)notif;
#end

Related

Objective-C - Errors when passing a variable into function

What is the easiest way to pass a var into another function ?
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
NSLog(#"%#", started);
}
I tried:
Defined a global vars:
extern NSString *started;
When I set the NSString directly and passing into another function, it works well:
-(void) startTracking:(CDVInvokedUrlCommand*)command {
started = #"testing";
}
But it doesn't work:
-(void) startTracking:(CDVInvokedUrlCommand*)command {
NSString* myarg = [command.arguments objectAtIndex:0]; // http://docs.phonegap.com/en/2.5.0/guide_plugin-development_ios_index.md.html#Developing%20a%20Plugin%20on%20iOS_writing_an_ios_cordova_plugin
started = myarg;
}
(I'm a objective-C beginner, don't understand it well)
EDIT: Seems like it only crashed when I put the app into background.
Depending on wether you are using ARC, you have to retain the object. You'll probably want to use a property on your class:
in your header:
#property(strong) NSString *started;
in implementation:
-(void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
NSLog(#"%#", self.started);
}
-(void) startTracking:(CDVInvokedUrlCommand*)command {
self.started = #"testing";
}
-(void) startTracking:(CDVInvokedUrlCommand*)command {
NSString* myarg = [command.arguments objectAtIndex:0];
self.started = myarg;
}
Mate, seems like you want to track the date you started receiving location information.
How about doing it like this:
// Your .h file
#interface MyClass <CLLocationManagerDelegate>
{
BOOL hasStartedUpdatingLocation;
NSDate *startDate;
CLLocationManager *locationManager;
}
...
// Your .m file
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
// ---------------------------------------------------------------
// if has NOT started updating location, record start date
// otherwise, do not execute this if statement
// ---------------------------------------------------------------
if(!hasStartedUpdatingLocation)
{
hasStartedUpdatingLocation = YES;
// this if statement should only execute once
startDate = [NSDate date]; // get the current date and time
}
else
{
// do something else
}
}

CLLocationManager is not updating location

This is related to my other question but I am openning a new one with iOS tags only because it could be that the problem is on native side.
Problem: location manager is not updating location. I tried reading locationManager.location and it always gives me one cached location.
Then I modified the code to use CLLocationManagerDelegate and -(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {}
is never being called.
my .h file:
#import "TiModule.h"
#import <CoreLocation/CoreLocation.h>
#import <CoreMotion/CoreMotion.h>
#interface TiMovementModule : TiModule<CLLocationManagerDelegate>
#property (nonatomic, retain) CLLocationManager *locationManager;
#property (nonatomic, readonly) NSDictionary *currentMovement;
- (void)startMovementUpdates:(id)args;
- (void)stopMovementUpdates:(id)args;
#end
.m
/**
* Your Copyright Here
*
* Appcelerator Titanium is Copyright (c) 2009-2010 by Appcelerator, Inc.
* and licensed under the Apache Public License (version 2)
*/
#import "TiMovementModule.h"
#import "TiBase.h"
#import "TiHost.h"
#import "TiUtils.h"
#interface TiMovementModule ()
#property (nonatomic, retain) CMMotionManager *motionManager;
#end
#implementation TiMovementModule
#synthesize motionManager, locationManager;
#pragma mark Internal
// this is generated for your module, please do not change it
-(id)moduleGUID
{
return #"3d2abdb6-bafb-451c-931d-a979dcc1ea78";
}
// this is generated for your module, please do not change it
-(NSString*)moduleId
{
return #"ti.movement";
}
#pragma mark Lifecycle
-(void)startup
{
// this method is called when the module is first loaded
// you *must* call the superclass
[super startup];
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
motionManager = [[CMMotionManager alloc] init];
NSLog(#"[INFO] %# loaded",self); //this prints
}
-(void)shutdown:(id)sender
{
// this method is called when the module is being unloaded
// typically this is during shutdown. make sure you don't do too
// much processing here or the app will be quit forceably
// you *must* call the superclass
[super shutdown:sender];
}
#pragma mark Cleanup
-(void)dealloc
{
self.motionManager = nil;
self.locationManager = nil;
[super dealloc];
}
#pragma Public APIs
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSLog(#"I AM HEREEEEEE!!!!!!!"); //this never prints
}
- (void)startMovementUpdates:(id)args
{
NSLog(#"[INFO] starting updates...");
[self.locationManager startUpdatingLocation];
[motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXTrueNorthZVertical];
NSLog(#"[INFO] started updates."); //this prints
}
- (void)stopMovementUpdates:(id)args
{
[locationManager stopUpdatingLocation];
[motionManager stopDeviceMotionUpdates];
}
- (id)currentMovement
{
NSDictionary *location = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithDouble:locationManager.location.coordinate.longitude], #"longitude",
[NSNumber numberWithDouble:locationManager.location.coordinate.latitude], #"latitude",
[NSNumber numberWithDouble:locationManager.location.altitude], #"altitude",
nil];
NSDictionary *rotation = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithDouble:motionManager.deviceMotion.attitude.roll], #"roll",
[NSNumber numberWithDouble:motionManager.deviceMotion.attitude.pitch], #"pitch",
[NSNumber numberWithDouble:motionManager.deviceMotion.attitude.yaw], #"yaw",
nil];
NSDictionary *movementData = [NSDictionary dictionaryWithObjectsAndKeys:
location, #"location",
rotation, #"rotation",
nil];
return movementData; // here I pull location and it always gives me cached location.
}
#end
You should try putting a breakpoint on this line:
[self.locationManager startUpdatingLocation];
to see if you ever get there as there is no evidence from your code that it ever gets executed.
The answer is that location updating has to be done from the main thread.
In Titanium you just have to call ENSURE_UI_THREAD at the function beginning.

How to update a UILabel in Xcode programmatically without XIB files?

I'm stuck :(
In my application I require an update from CLLocationManager every time it gets an update to a new position. I am not using XIB/NIB files, everything I coded I have done programmatically. To the code:
the .h
#interface TestViewController : UIViewController
UILabel* theLabel;
#property (nonatomic, copy) UILabel* theLabel;
#end
the .m
...
-(void)loadView{
....
UILabel* theLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0,0.0,320.0,20.0)];
theLabel.text = #"this is some text";
[self.view addSubView:theLabel];
[theLabel release]; // even if this gets moved to the dealloc method, it changes nothing...
}
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
NSLog(#"Location: %#", [newLocation description]);
// THIS DOES NOTHING TO CHANGE TEXT FOR ME... HELP??
[self.view.theLabel setText:[NSString stringWithFormat: #"Your Location is: %#", [newLocation description]]];
// THIS DOES NOTHING EITHER ?!?!?!?
self.view.theLabel.text = [NSString stringWithFormat: #"Your Location is: %#", [newLocation description]];
}
...
Any ideas, or help?
(this was all hand jammed so please forgive me if it looks kinda gacked) I can provide more info if needed.
Your loadView method is wrong. You do not set the instance variable properly but instead you generate a new local variable. Change it to the following by omitting the UILabel * and do not release it because you want to keep a reference around to the label to set the text later.
-(void)loadView{
....
theLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0,0.0,320.0,20.0)];
theLabel.text = #"this is some text";
[self.view addSubView:theLabel];
}
- (void) dealloc {
[theLabel release];
[super dealloc];
}
Then later directly access the variable like this:
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
NSLog(#"Location: %#", [newLocation description]);
theLabel.text = [NSString stringWithFormat: #"Your Location is: %#", [newLocation description]];
}
Are you synthesizing theLabel in your .m file...? If not, you need to, I believe.

Error setting property on CLLocation subclass

I've defined this subclass of CLLocation
MyLocation.h
#interface MyLocation: CLLocation {
NSString *datum;
NSString *ellipsoid;
}
#property(nonatomic,retain) NSString *ellipsoid;
#property(nonatomic,retain) NSString *datum;
MyLocation.m
#synthesize datum,ellipsoid;
Now I get a CLLocation instance through the location manager delegate
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
//self.myPosition is a MyLocation myPosition;
self.myPosition = newLocation;
[myPosition setDatum: #"WGS 84"];
[myPosition setEllipsoid: #"WGS 84"];
}
When I do the self.myPosition = newLocation, I get an UNCAUGHT EXCEPTION and it dies
I've also tried this way with same results:
self.myPosition = (MyLocation *)newLocation;
newLocation is not an instance of ur subclass so doing that assignment or casting throws an error because it's an invalid cast, u should be seting the values of ur subclass that u want fromthe cllocation object something like
self.myPosition=[[MyLocation alloc] initWithCoordinate:newLocation.coordinate]
and so on

What's the easiest way to get the current location of an iPhone?

I already know how to use the CLLocationManager, so I could do it the hard way, with delegates and all that.
But I'd like to have a convenience method that just gets the current location, once, and blocks until it gets the result.
What I do is implement a singleton class to manage updates from core location. To access my current location, I do a CLLocation *myLocation = [[LocationManager sharedInstance] currentLocation]; If you wanted to block the main thread you could do something like this:
while ([[LocationManager sharedInstance] locationKnown] == NO){
//blocking here
//do stuff here, dont forget to have some kind of timeout to get out of this blocked //state
}
However, as it has been already pointed out, blocking the main thread is probably not a good idea, but this can be a good jumping off point as you are building something. You will also notice that the class I wrote checks the timestamp on location updates and ignores any that are old, to prevent the problem of getting stale data from core location.
This is the singleton class I wrote. Please note that it is a little rough around the edges:
#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>
#interface LocationController : NSObject <CLLocationManagerDelegate> {
CLLocationManager *locationManager;
CLLocation *currentLocation;
}
+ (LocationController *)sharedInstance;
-(void) start;
-(void) stop;
-(BOOL) locationKnown;
#property (nonatomic, retain) CLLocation *currentLocation;
#end
#implementation LocationController
#synthesize currentLocation;
static LocationController *sharedInstance;
+ (LocationController *)sharedInstance {
#synchronized(self) {
if (!sharedInstance)
sharedInstance=[[LocationController alloc] init];
}
return sharedInstance;
}
+(id)alloc {
#synchronized(self) {
NSAssert(sharedInstance == nil, #"Attempted to allocate a second instance of a singleton LocationController.");
sharedInstance = [super alloc];
}
return sharedInstance;
}
-(id) init {
if (self = [super init]) {
self.currentLocation = [[CLLocation alloc] init];
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
[self start];
}
return self;
}
-(void) start {
[locationManager startUpdatingLocation];
}
-(void) stop {
[locationManager stopUpdatingLocation];
}
-(BOOL) locationKnown {
if (round(currentLocation.speed) == -1) return NO; else return YES;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
//if the time interval returned from core location is more than two minutes we ignore it because it might be from an old session
if ( abs([newLocation.timestamp timeIntervalSinceDate: [NSDate date]]) < 120) {
self.currentLocation = newLocation;
}
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
UIAlertView *alert;
alert = [[UIAlertView alloc] initWithTitle:#"Error" message:[error description] delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
[alert release];
}
-(void) dealloc {
[locationManager release];
[currentLocation release];
[super dealloc];
}
#end
There is no such convenience and you shouldn't create your own. "Blocks until it gets the result" is extremely bad programming practice on a device like the iPhone. It can take seconds to retrieve a location; you should never make your users wait like that, and delegates ensure they don't.
There are no "convenience methods" unless you code them yourself, but you'd still need to implement the delegate methods in whatever custom code you use to make things "convenient."
The delegate pattern is there for a reason, and as delegates are a big part of Objective-C, I recommend you get comfortable with them.
I appreciated the answer by Brad Smith. Implementing it I discovered that one of the methods he employs is deprecated as of iOS6. To write code that will work with both iOS5 and iOS6, use the following:
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
if (abs([[locations lastObject] timeIntervalSinceDate:[NSDate date]]) < 120) {
[self setCurrentLocation:[locations lastObject]];
}
}
// Backward compatibility with iOS5.
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSArray *locations = [NSArray arrayWithObjects:oldLocation, newLocation, nil];
[self locationManager:manager didUpdateLocations:locations];
}
I simplified and combined multiple answers to where the location is only updated if it's valid.
It also works under OSX as well as iOS.
This assumes the use-case where the current location is suddenly desired by the user. If it takes more than 100 ms in this example, it's considered an error. (Assumes the GPS IC &| Wifi (Apple's Skyhook clone) is already fired up and has a good fix already.)
#import "LocationManager.h"
// wait up to 100 ms
CLLocation *location = [LocationManager currentLocationByWaitingUpToMilliseconds:100];
if (!location) {
NSLog(#"no location :(");
return;
}
// location is good, yay
https://gist.github.com/6972228