I am pretty new to iOS coding. I need some help to fix an issue with CLLocation.
I use didUpdateLocations to retrieve location and speed information. I am able to get these details and display using NSLog.
However, I am unable to display it on UIview. How do I fix it?
Below is my code.
I use CoreLocationController to retrieve my location details.
#implementation CoreLocationController
#synthesize locMgr;
#synthesize location;
- (id)init {
self = [super init];
if(self != nil) {
self.locMgr = [[CLLocationManager alloc] init];
self.locMgr.delegate = self;
}
return self;
}
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations
{
DriveTrapViewController *driveTrapViewController = [[DriveTrapViewController alloc]init];
self.location = [locations lastObject];
//NSLog(#"Speed, %f", self.location.speed);
[driveTrapViewController locationUpdate:(CLLocation *)self.location];
}
I send the retrieved details back to DriveTrapViewController. I am able to display them using NSLog. But self.speedText.text is always NULL and nothing is showing on the screen.
#implementation DriveTrapViewController
#synthesize CLController;
#synthesize speedText = _speedText;
- (void)viewDidLoad {
[super viewDidLoad];
CLController = [[CoreLocationController alloc] init];
CLController.delegate = self;
[CLController.locMgr startUpdatingLocation];
}
- (void)locationUpdate:(CLLocation *)location
{
NSString *speed = [NSString stringWithFormat:#"SPEED: %f", location.speed];
NSLog(#"Speed %#",speed);
self.speedText.text = speed;
}
How do I fix this? Any help is appreciated. Thanks
Every time your locationManager:didUpdateLocations: method is called, it creates a new DriveTrapViewController, which is destroyed as soon as the method finishes. Maybe you should use an ivar for this instead. That way it wouldn't be created and destroyed every time.
Also, both objects seem to be making instances of the other. Instead of a Core Location problem, this is a general architecture-type problem.
I'd suggest that at this stage you should just have a CLLocationManager variable inside your DriveTrapController instead of trying to break it out into a controller of it's own.
Try CLController.locMgr.desiredAccuracy = kCLLocationAccuracyBestForNavigation;. this should include some extra information about the users location, speed, bearing etc...
Related
I have a problem.
I have a ViewController wherein I have a property that's a subclass of NSManagedObject. In the same viewController, in the viewDidAppear method, I call the new requestAlwaysAuthorization method on a CLLocationManager object because I need to use the GPS. My issue is that after the user has allowed me to use the device location, the core data object (the NSManagedObject) has all nil fields.
I've tested removing my [_locationManager requestAlwaysAuthorization]; line and that results in non-corrupted NSManagedObject but then again I can't use location on iOS8+ if I skip this line!
I'm using the Magical Records library to handle core data but I can't see how that could be a factor here.
Does anybody have any idea what might be going on?
EDIT: code
In the .h file of my viewController (VC) I have
#property (strong, nonatomic) CDMission *mission;
and in the .m file of the same VC I have:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[LocationTracker sharedTracker] wakeUpLocationManager];
}
The important methods in the LocationTracker class look like this:
+ (instancetype)sharedTracker {
static LocationTracker *sharedTracker = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedTracker = [[self alloc] init];
});
return sharedTracker;
}
# pragma mark - Getters and setters
- (CLLocationManager *)locationManager {
if (!_locationManager) {
_locationManager = [[CLLocationManager alloc] init];
if ([_locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[_locationManager requestAlwaysAuthorization];
}
_locationManager.activityType = CLActivityTypeFitness;
_locationManager.pausesLocationUpdatesAutomatically = YES;
_locationManager.delegate = self;
_locationManager.desiredAccuracy = kCLLocationAccuracyBest;
}
return _locationManager;
}
- (void)wakeUpLocationManager {
[self locationManager];
}
when I put a break point in viewDidAppear I can see how my CDMission object (that's my NSManagedObject) is fine but when I press a button after allowing location and put a breakpoint in the button's selector I see how my object is messed up.
Second edit:
The CDMission object that is a property in my VC is passed into the VC from another viewcontroller. Thus, there is no core data code relevant to my issue. It's just that the NSManagedObject CDMission becomes corrupted after the user answers the permission question for the location.
I'm using MapBox and RMTileCache's beginBackgroundCacheForTileSource: to preload tiles before rendering maps in my client app. I want to preload the tiles for the currently visible area of the map from zoom level 3 thru zoom level 7. So, I pass the current bounds of my mapView using the rect returned from [mapView latitudeLongitudeBoundingBox]. My ViewController implements RMTileCacheBackgroundDelegate so as to get callbacks as the caching occurs and then completes. I do receive callbacks on didBeginBackgroundCacheWithCount:forTileSource: and didBackgroundCacheTile:withIndex:ofTotalTileCount: However, tileCacheDidFinishBackgroundCache: is never called.
I stepped thru the source of RMTileCache and noticed that the actual count of cached tiles in "progTile" never reaches the calculated total count in "totalTiles". Therefore, the final callback, tileCacheDidFinishBackgroundCache:, is not reached.
I'm not sure how to change (or if I should change) the totalTiles calculation. Perhaps I am passing the wrong viewing rect in my initial call? I'm not totally clear if that is correct. I can create a simple fix that calls tileCacheDidFinishBackgroundCache: in the case that caching simply finishes, but this seems to just hide the issue. Any guidance here would be appreciated.
For reference, my test code is straight forward:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
RMMapboxSource *onlineSource = [[RMMapboxSource alloc] initWithMapID:#"appleweed.control-room"];
mapView = [[RMMapView alloc] initWithFrame:self.view.bounds andTilesource:onlineSource];
//mapView.delegate = self;
mapView.bouncingEnabled = YES;
mapView.clusteringEnabled = YES;
mapView.clusterMarkerSize = CGSizeMake(40, 40);
mapView.clusterAreaSize = CGSizeMake(40, 40);
mapView.zoom = 1;
RMSphericalTrapezium rect = [mapView latitudeLongitudeBoundingBox];
mapView.tileCache.backgroundCacheDelegate = self;
[mapView.tileCache beginBackgroundCacheForTileSource:mapView.tileSource
southWest:rect.southWest
northEast:rect.northEast
minZoom:3.0
maxZoom:7.0];
}
- (void)tileCache:(RMTileCache *)tileCache didBeginBackgroundCacheWithCount:(int)tileCount forTileSource:(id <RMTileSource>)tileSource {
NSLog(#"start");
}
- (void)tileCache:(RMTileCache *)tileCache didBackgroundCacheTile:(RMTile)tile withIndex:(int)tileIndex ofTotalTileCount:(int)totalTileCount {
// float percentComplete = ((float)tileIndex / (float)totalTileCount) * 100;
//NSString *update = [NSString stringWithFormat:#"%.0f%%", percentComplete];
//NSLog(#"%#",update);
}
- (void)tileCacheDidFinishBackgroundCache:(RMTileCache *)tileCache {
NSLog(#"DONE!");
}
- (void)tileCacheDidCancelBackgroundCache:(RMTileCache *)tileCache {
NSLog(#"Canceled!");
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Is it stopping at the same tile count each time? Is there a map view visible at the same time? If you pause your app in the debugger, what is it doing once it seems to have stopped downloading tiles in the background?
I am unable to figure out why the roll, pitch and yaw values are giving 0.0000 when logged.. I am sure it is a something that i miss but i cant figure it out..
This is the code:
//ViewController.m
#import "ViewController.h"
#interface ViewController (){
}
#property (nonatomic) CMMotionManager *motionManager;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.motionManager = [[CMMotionManager alloc] init];
CMDeviceMotion *devMotion = [[CMDeviceMotion alloc]init];
if ([self.motionManager isDeviceMotionAvailable]) {
NSLog(#"Device Motion Available");
[self.motionManager setDeviceMotionUpdateInterval:1.0/30.0];
// Pull mechanism is used
[self.motionManager startDeviceMotionUpdates];
}
devMotion = self.motionManager.deviceMotion;
NSLog(#"Roll Pitch and Yaw are %f, %f, %f",devMotion.attitude.roll, devMotion.attitude.pitch, devMotion.attitude.yaw);
}
I have gone thru this similar question: SO Question
Please help me understand this..
Thanks..
Updated Code:
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.motionManager = [[CMMotionManager alloc] init];
if ([self.motionManager isDeviceMotionAvailable]) {
NSLog(#"Device Motion Available");
[self.motionManager setDeviceMotionUpdateInterval:1.0/30.0];
// Pull mechanism is used
[self.motionManager startDeviceMotionUpdates];
}
CMDeviceMotion *devMotion = self.motionManager.deviceMotion;
NSLog(#"*Roll,Pitch and Yaw are %f, %f, %f",devMotion.attitude.roll, devMotion.attitude.pitch, devMotion.attitude.yaw);
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0/30.0 target:self selector:#selector(updateValues:) userInfo:nil repeats:YES];
}
-(void) updateValues:(NSTimer *)timer{
CMDeviceMotion *currDeviceMotion = self.motionManager.deviceMotion;
NSLog(#"Roll Pitch and Yaw are %f, %f, %f",currDeviceMotion.attitude.roll, currDeviceMotion.attitude.pitch, currDeviceMotion.attitude.yaw);
}
This code also has a large part of its initial values as 0.000000 . After that it starts to get values... So i guess there is some delay for startDeviceMotionUpdates for providing values to deviceMotion. So looks like i need to figure out how to save the first non zero values.
Notice that your devMotion variable won’t hold usable values (or any at all) right away as CMMotionManager takes a while to update the deviceMotion property after startDeviceMotionUpdates. So you should have some sort of timer that fires periodically and reads that property. Your linked article does something similar.
As long as the gyroscope is starting up, the deviceMotion property will be null (you’re only seeing 0.0 because messages to nil return zero).
As a sidenote: your call to [[CMDeviceMotion alloc] init] is useless as you then override the variable the result was assigned to with self.motionManager.deviceMotion.
I would like to find out userlocation coordinate while my app is loading. I have implemented following code but it returns 0.000
mapView.showsUserLocation=YES;
CLLocationCoordinate2D annotationCoordinate;
annotationCoordinate.latitude=mapView.userLocation.location.coordinate.latitude;
NSLog(#"%f",annotationCoordinate.latitude);
I could not able to figure out. Any help?
First of all you should take into account that it takes time to retrieve the user location. Moreover the user can disable the location service for your application or even the location service can be unavailable during the connectivity conditions. So you'd better to rethink your application starting procedures.
When you make up you decision take a look at mapView:didUpdateUserLocation: method of MKMapViewDelegate protocol. After this method fires out the location can be available via the userLocation property of the MKMapView.
UPDATE
In case you want to open map view with the user location already checked, you may consider using CLLocationManager and CLLocationManagerDelegate. This way you can check if the location service is available and open map view after the method locationManager:didUpdateToLocation:fromLocation: fires up.
For the complete info take a look at Getting the User’s Location programing guide
You can not get users location coordinate in view did load what you need to do is using the delegate method below.
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
NSLog(#"coordinates = %f,%f", mapView.userLocation.coordinate.latitude,
mapView.userLocation.coordinate.longitude);
}
Make sure your map object is connected with the delegate
Just tested it should work
1) Add the FrameWork CoreLocation and Mapkit
2) In ViewController.h file
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#interface MapViewController : UIViewController<MKMapViewDelegate,CLLocationManagerDelegate>
{
CLLocationManager *locationManager;
}
#property (nonatomic, strong) IBOutlet MKMapView *mapView;
3) In viewController.m file
- (void)viewDidLoad
{
[super viewDidLoad];
self.mapView.delegate = self;
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate=self;
locationManager.distanceFilter = kCLDistanceFilterNone; // whenever we move
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters; // 100 m
[locationManager startUpdatingLocation];
}
now in didUpdateUserLocation
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(userLocation.coordinate, 800, 800);
[self.mapView setRegion:[self.mapView regionThatFits:region] animated:YES];
// Add an annotation
MKPointAnnotation *point = [[MKPointAnnotation alloc] init];
point.coordinate = userLocation.coordinate;
point.title = #"Where am I?";
point.subtitle = #"I'm here!!!";
[self.mapView addAnnotation:point];}
4) Now add Mapview in your UI
NOTE: select MapView and goto Attribute Inspector and CKECK Mark the Shows user location Under BEHAVIOUR
I think you should use this:
#import <CoreLocation/CoreLocation.h>
Then in viewDidLoad method:
CLLocationManager *locationManager = [[CLLocationManager alloc] init];
[locationManager startUpdatingLocation];
[mapView setRegion:MKCoordinateRegionMake(locationManager.location.coordinate, MKCoordinateSpanMake(0.2, 0.2))];
mapView.showsUserLocation = YES;
NSLog(#"%f",mapView.region.center.latitude);
Okay, so I just managed to fix it myself too. What no one is telling you is that you need to set a key/value pair in the info.plist.
Depending on what method (requestAlwaysAuthorization or requestWhenInUseAuthorization) you need to add a key under the 'Information Property List' dictionary. For the first method use: NSLocationAlwaysUsageDescription and for the second method use: NSLocationWhenInUseUsageDescription .
The value fields of both keys can be set to whatever string you would like to display to the user.
Both keys at the top are what we are looking for.
Now you should see a dialogue that prompts you for confirmation.
PS: No tutorial on the internet listed this step of the process, but when you read the documentation for each of those methods (requestWhenInUse on CLLocationManager) it does mention that nothing gets displayed without those two keys.
I have an app that uses a GoogleMap view.
Into that app, I want to display some custom anotations, and a custom view for the userLocation. When the location manager gives a heading, I want to display that custom view for userLocation, if it fails to deliver one, I want the default blue animated dot to come back, etc...
Project and Source code available HERE
Well, there are 2 problems here :
1st problem, THE REALLY BAD ONE. You can play for hours with that tiny app. But... Launch it, wait it loads, send it to the background, put your iPhone in sleep mode, wait a minute, wake up your iPhone, launch the app (that wakes it up) : crash, EXC_BAD_ACCESS. Do the same thing not putting the iPhone in sleep mode : no crash. Not waiting for a minute but a few seconds, no crash. 10 seconds, no crash, 20 seconds, no crash, nears 30 seconds, perhaps a crash, near a minute, crash !
2nd problem (bonus :-) ), that can be linked with the first one, but that is not really the heart of this question : the solution I use to achieve the userPinLocation to update from/to the blue pin seems to have a really bad side effect : the userLocation does not move on the map as you move in the street. For any answer for that 2nd problem, if it's not directly linked with the crash, please use comments not answers. This is not the heart of the question... I think...
To find the faulty code, I've reduced my app at its minimum. That gives the following code (some tips in the source code as comments) :
EXC_BAD_ACCESS_TestViewController.h
#import <MapKit/MapKit.h>
#interface EXC_BAD_ACCESS_TestViewController : UIViewController<CLLocationManagerDelegate> {
CLLocationManager* locationMgr;
NSMutableArray* customAnnotations;
CLLocationDirection userHeading;
MKMapView* myMapView;
}
#property(nonatomic, retain) CLLocationManager* locationMgr;
#property(nonatomic, retain) NSMutableArray* customAnnotations;
#property(nonatomic, assign) CLLocationDirection userHeading;
#property(nonatomic, retain) IBOutlet MKMapView* myMapView;
- (void) loadAnnotations;
#end
EXC_BAD_ACCESS_TestViewController.m
// On first launch, due to code simplification, the app may crash when asked authorization to use geo hardware. Just kill/relaunch after accepting.
// Breakpoints on each method entry : EXC_BAD_ACCESS crash without any break-stop
#import "EXC_BAD_ACCESS_TestViewController.h"
#import "CustomAnnotation.h"
#implementation EXC_BAD_ACCESS_TestViewController
#synthesize locationMgr, customAnnotations, myMapView, userHeading;
// ===========================================================================================================
-(id) initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (!self) return nil;
self.userHeading = -1;
return self;
}
// ===========================================================================================================
- (void) viewDidLoad
{
[self loadAnnotations];
self.myMapView.mapType = MKMapTypeStandard;
self.myMapView.showsUserLocation = YES;
// -----------------------------------------------
// Just comment this sole line : no more crash
// -----------------------------------------------
for (CustomAnnotation* pin in self.customAnnotations) [self.myMapView addAnnotation:pin];
// locationManager
self.locationMgr = [[CLLocationManager alloc] init];
self.locationMgr.desiredAccuracy = kCLLocationAccuracyBest;
self.locationMgr.distanceFilter = 1.0;
self.locationMgr.headingFilter = 1.0;
self.locationMgr.purpose = #"Some purpose.";
self.locationMgr.delegate = self;
[self.locationMgr startUpdatingHeading];
[super viewDidLoad];
}
// ===========================================================================================================
- (void) loadAnnotations
{
// Code for testing, real code gets the datas using another way of doing, as simple as this one
self.customAnnotations = [NSMutableArray array];
double latitude = 45.0;
double longitude = -45.0;
for (int i=1; i<=400; i++) {
CLLocationCoordinate2D pinLocation = CLLocationCoordinate2DMake(latitude, longitude);
CustomAnnotation* pin = [[[CustomAnnotation alloc] initWithCoordinate:pinLocation] autorelease];
[self.customAnnotations addObject:pin];
latitude += 0.01;
longitude += 0.01;
}
}
// ===========================================================================================================
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation
{
MKAnnotationView* pinView = nil;
NSString* annotationIdentifier;
UIImage* pinImage;
BOOL canShowCallout;
// User location
if ([annotation isKindOfClass:[MKUserLocation class]] && self.userHeading >= 0) {
annotationIdentifier = #"UserLocationAnnotation";
pinImage = [UIImage imageNamed:#"custom_userlocation.png"];
canShowCallout = NO;
}
// Custom Pin
else if ([annotation isKindOfClass:[CustomAnnotation class]]) {
annotationIdentifier = #"CustomAnnotation";
pinImage = [UIImage imageNamed:#"custom_pin.png"];
canShowCallout = YES;
}
// Others
else {
return nil;
}
pinView = (MKAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
if (pinView) {
pinView.annotation = annotation;
}
else {
pinView = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:annotationIdentifier] autorelease];
if (pinView) {
pinView.image = pinImage;
pinView.canShowCallout = canShowCallout;
if (canShowCallout) {
pinView.calloutOffset = CGPointMake(-5, 5);
pinView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
}
}
return pinView;
}
// -----------------------------------------------
// Just comment this method : no more crash
// -----------------------------------------------
// ===========================================================================================================
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
if (!self.myMapView) return;
if (!self.myMapView.userLocation) return;
CLLocationDirection compassHeading_True = newHeading.trueHeading;
CLLocationDirection compassHeading_Magnetic = newHeading.magneticHeading;
CLLocationDirection heading;
if (compassHeading_True == -1) heading = compassHeading_Magnetic;
else if (newHeading.headingAccuracy >= 0) heading = compassHeading_True;
else heading = -1;
// If we get/loose the heading, update pin image
// I didn't found any other solution to force the user pin to update its display.
if ((self.userHeading == -1 || heading == -1) && self.userHeading != heading) {
[self.myMapView removeAnnotation:self.myMapView.userLocation];
self.userHeading = heading;
[self.myMapView addAnnotation:self.myMapView.userLocation];
}
self.userHeading = heading;
// ------------------------------------
// Some non bugued code was there
// ------------------------------------
}
// ===========================================================================================================
- (void) dealloc
{
self.myMapView = nil;
self.customAnnotations = nil;
self.locationMgr = nil;
[super dealloc];
}
#end
CustomAnnotation.h
#import <MapKit/MapKit.h>
#interface CustomAnnotation : NSObject<MKAnnotation> {
CLLocationCoordinate2D coordinate;
}
#property(nonatomic, assign) CLLocationCoordinate2D coordinate;
- (id)initWithCoordinate:(CLLocationCoordinate2D) c;
#end
CustomAnnotation.m
#import "CustomAnnotation.h"
#implementation CustomAnnotation
#synthesize coordinate;
// ===========================================================================================================
- (id)initWithCoordinate:(CLLocationCoordinate2D) c
{
self = [super init];
if (!self) return nil;
self.coordinate = c;
return self;
}
// ===========================================================================================================
- (NSString*)subtitle
{
return #"";
}
// ===========================================================================================================
- (NSString*)title
{
return #"";
}
#end
I tried breakpoints in each methods, trying to enable Zombies with environment variables (but as this needs to be ran on the device, I'm not sure they've been really activated), without any start of solution... I still don't have any idea of what's going wrong.
Do you see how to solve that EXC_BAD_ACCESS problem ?
For any answer for the 2nd problem, please use comments and not answers if it don't solve the crash. This problem is not the heart of the question as far as I've seen.
Tip : I've put 2 comments in the code above where I found some start of solution. There are 2 places, that does not seems connected, that can be put out of the code one OR the other to solve the problem. What makes me crazy is the OR.
Runned on an iPhone 4 ios 4.2.1
[self.myMapView removeAnnotation:self.myMapView.userLocation];
This line is causing the crash from stacktrace this is what I've seen.
When I saw exc_bad_access I did a bt in gdb. I have added Guard Malloc breakpoint. Looking at the stack it said about removing annotation.
You can try overriding a method called didReceiveMemoryWarning in your view controller .m file.
What happens sometimes is that memory on the device gets low due to running apps and IOS sends a message to the app. Now the trouble is when you don't override memory warning method, its default action is to remove any subviews from memory which are not visible. This can lead to zombie variables when your application runs.
Check out the apple doc