How to pull data point from Firebase using Obj-C? - objective-c

I'm saving a users zip code to the Firebase database and want to query that database on app launch to see if the user has input their zip code already or if they're a brand new user.
I've posted my code before. I pulled the sample code from the Firebase docs, but it seems that my app is never even running the following code to get the value
[[[_ref child:#"user"] child:userID] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {...
What am I missing out on?
#import "FollowingVC.h"
#import <FirebaseDatabase/FirebaseDatabase.h>
#import Firebase;
#interface FollowingVC ()
#property NSString *uid;
#property FIRDatabaseReference *ref;
#property NSString *zipcode;
#end
#implementation FollowingVC
- (void)viewDidLoad {
[super viewDidLoad];
[self createAuthorizedUser];
[self checkForZipCode];
}
-(void)createAuthorizedUser
{
[[FIRAuth auth]
signInAnonymouslyWithCompletion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
if (!error) {
self.uid = user.uid;
self.ref=[[FIRDatabase database]reference];
}
}];
}
-(void)checkForZipCode
{
NSString *userID = [FIRAuth auth].currentUser.uid;
[[[_ref child:#"user"] child:userID] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
// Get user value
self.zipcode = snapshot.value[#"zip code"];
NSLog(#"It worked: %#", self.zipcode);
// ...
} withCancelBlock:^(NSError * _Nonnull error) {
NSLog(#"%#", error.localizedDescription);
}];
}
#end

Firebase is asynronous and you need to allow time for events to complete before moving on in the app.
In this case, you should call [self checkForZipCode] inside the sign-in block after self.uid is populated.
Otherwise you run the risk of the checkForZipCode function running before the self.uid is populated.
Let Firebase control the flow of the app - and don't try to use Firebase synchronously as it will get you into trouble due to internet lag etc.

Related

NSPersistentDocument fails when saving a previously locked document

When opening a locked file using my NSPersistentDocument subclass I get the following message in the console:
Attempt to add read-only file at path [URL] read/write. Adding
it read-only instead. This will be a hard error in the future; you
must specify the NSReadOnlyPersistentStoreOption.
The document window title is '(document name) - Locked'. After the user unlocks it, makes a change and then attempts to save, the save fails with the error
An error occurred while saving.
It seems that NSPersistentDocument fails to recognize that the user has unlocked the document and doesn't reopen it in read/write mode. Is this a bug in NSPersistentDocument or am I missing something here?
I am not overriding any of the file I/O methods in NSPersistentDocument.
Ah, ok automatic file locking.
That happens for auto-save documents not accessed in a while.
The typical approach is to notice the lock before creating the core data stack and put up a dialog asking the user to unlock the file.
If they agree to unlock the file, you simply unlock it and run as normal.
If they don't agree to unlock it, you copy it or open it readonly. Of course, you could simply bypass the user's preference and automatically unlock the file anyway, but that's probably not very nice.
Here is a category that should help you determine if a file is locked, and also lock/unlock the file.
Note, that this is entirely separate from the files mode being changed to read-only, but you can handle it in a similar manner.
Category interface
#interface NSFileManager (MyFileLocking)
- (BOOL)isFileLockedAtPath:(NSString *)path;
- (BOOL)unlockFileAtPath:(NSString*)path error:(NSError**)error;
- (BOOL)lockFileAtPath:(NSString*)path error:(NSError**)error;
#end
Category implementation
#implementation NSFileManager (MyFileLocking)
- (BOOL)isFileLockedAtPath:(NSString *)path {
return [[[self attributesOfItemAtPath:path error:NULL]
objectForKey:NSFileImmutable] boolValue];
}
- (BOOL)unlockFileAtPath:(NSString*)path error:(NSError**)error {
return [self setAttributes:#{NSFileImmutable:#NO}
ofItemAtPath:path
error:error];
}
- (BOOL)lockFileAtPath:(NSString*)path error:(NSError**)error {
return [self setAttributes:#{NSFileImmutable:#YES}
ofItemAtPath:path
error:error];
}
#end
Then, you can call [[NSFileManager defaultManager] isFileLockedAtPath:path] to determine if it is locked, and if it is, throw up a dialog asking the user what to do about it. You can then unlock it and open the stack as normal, or leave it locked and open the stack read-only, which will prevent saves from changing the file store.
Note that you can also monitor the file, and know when it changes from locked/unlocked and respond accordingly.
For Apple's guidelines on this, see https://developer.apple.com/library/mac/documentation/DataManagement/Conceptual/DocBasedAppProgrammingGuideForOSX/StandardBehaviors/StandardBehaviors.html
EDIT
Ok. I would have liked for NSPersistentDocument to replicate the
behavior in NSDocument - where the prompt to unlock comes only when an
edit is attempted. What you're saying is that there is no such feature
in NSPersistentDocument? – Aderstedt
OK. I thought you were wanting to ask the user to unlock it so that it could be opened read/write.
If you want to "go with the flow" and open it read-only when necessary, then you should add a little customization to your NSPersistentDocument subclass.
First, you want to add a little state to keep track of whether or not the original options specified a read-only file.
#implementation MyDocument {
BOOL explicitReadOnly;
}
Then, you will want a couple of utility methods...
- (NSDictionary*)addReadOnlyOption:(NSDictionary*)options {
NSMutableDictionary *mutable = options ? [options mutableCopy]
: [NSMutableDictionary dictionary];
mutable[NSReadOnlyPersistentStoreOption] = #YES;
return [mutable copy];
}
- (NSDictionary*)removeReadOnlyOption:(NSDictionary*)options {
NSMutableDictionary *mutable = options ? [options mutableCopy]
: [NSMutableDictionary dictionary];
[mutable removeObjectForKey:NSReadOnlyPersistentStoreOption];
return [mutable copy];
}
Next, you want to provide your own persistent store coordinator configuration code. This allows you to provide the read-only option to the store when you create it. This method is automatically called when you build your document, all you need to do is provide an override implementation.
- (BOOL)configurePersistentStoreCoordinatorForURL:(NSURL *)url
ofType:(NSString *)fileType
modelConfiguration:(NSString *)configuration
storeOptions:(NSDictionary<NSString *,id> *)storeOptions
error:(NSError * _Nullable __autoreleasing *)error {
explicitReadOnly = [storeOptions[NSReadOnlyPersistentStoreOption] boolValue];
if (![[NSFileManager defaultManager] isWritableFileAtPath:url.path]) {
storeOptions = [self addReadOnlyOption:storeOptions];
}
return [super configurePersistentStoreCoordinatorForURL:url
ofType:fileType
modelConfiguration:configuration
storeOptions:storeOptions
error:error];
}
Also, notice that NSPersistentDocument implements the NSFilePresenter protocol. Thus, you can override a method and be notified whenever the file content or attributes are changed. This will notify you for any change to the file, including lock/unlock from within your application, the Finder, or any other mechanism.
- (void)presentedItemDidChange {
[self ensureReadOnlyConsistency];
[super presentedItemDidChange];
}
We then want to ensure that our persistent store remains consistent with the read-only properties of the file.
Here is one implementation, that just changes the store's readOnly property.
- (void)ensureReadOnlyConsistency {
NSURL *url = [self presentedItemURL];
BOOL fileIsReadOnly = ![[NSFileManager defaultManager] isWritableFileAtPath:url.path];
NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;
[psc performBlock:^{
NSPersistentStore *store = [psc persistentStoreForURL:url];
if (store) {
if (fileIsReadOnly) {
if (!store.isReadOnly) {
store.readOnly = YES;
}
} else if (!explicitReadOnly) {
if (store.isReadOnly) {
store.readOnly = NO;
}
}
}
}];
}
This works, but has one little hangup. If the store is originally opened with read-only options, then the very first time the readOnly attribute is set to NO, that first save throws (actually, it's the obtainPermanentIDsForObjects:error: call. Core data appears to catch the exception, but it is logged to the console.
The save continues, and nothing seems amiss. All the objects get saved, and the object IDs are properly obtained and recorded as well.
So, there is nothing that does not work that I can tell.
However, there is another more draconian option, but it avoids the aforementioned "issue." You can replace the store.
- (void)ensureReadOnlyConsistency {
NSURL *url = [self presentedItemURL];
BOOL fileIsReadOnly = ![[NSFileManager defaultManager] isWritableFileAtPath:url.path];
NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;
[psc performBlock:^{
NSPersistentStore *store = [psc persistentStoreForURL:url];
if (store) {
if (fileIsReadOnly != store.isReadOnly) {
NSString *type = store.type;
NSString *configuration = store.configurationName;
NSDictionary *options = store.options;
if (fileIsReadOnly) {
options = [self addReadOnlyOption:options];
} else if (!explicitReadOnly) {
options = [self removeReadOnlyOption:options];
}
NSError *error;
if (![psc removePersistentStore:store error:&error] ||
![psc addPersistentStoreWithType:type
configuration:configuration
URL:url
options:options
error:&error]) {
// Handle the error
}
}
}
}];
}
Finally, note that the notification happens when the operating system notices that the file has changed. When the file is locked/unlocked from within your application, you can get a faster notification.
You can override these two methods to get a little quicker response to the change...
- (void)lockWithCompletionHandler:(void (^)(NSError * _Nullable))completionHandler {
[super lockWithCompletionHandler:^(NSError * _Nullable error) {
if (completionHandler) completionHandler(error);
if (!error) [self ensureReadOnlyConsistency];
}];
}
- (void)unlockWithCompletionHandler:(void (^)(NSError * _Nullable))completionHandler {
[super unlockWithCompletionHandler:^(NSError * _Nullable error) {
if (completionHandler) completionHandler(error);
if (!error) [self ensureReadOnlyConsistency];
}];
}
I hope that's what you are looking for.

What is the best way to add user relations with Parse?

I have a textfield where users enter a username and click add.
Is this the best way to do this?:
Make query for name of user. If user does not exist, give warning message. If user does exist, add them to relations with this:
[self.friends addObject:user];
[friendsRelation addObject:user];
Another question is, how do I search for a user with a query and return an object?
Also, here are some variables I made in .h
#property (nonatomic, strong) NSArray *allUsers;
#property (nonatomic, strong) PFUser *currentUser;
#property (nonatomic, strong) NSMutableArray *friends;
#property (nonatomic, strong) PFUser *foundUser;
- (BOOL)isFriend:(PFUser *)user;
Check out the below code and you can tailor it to your more specific needs, but it generally does what you are asking for. I strongly advise you to read the documentation on all the methods in the code, and check some Parse tutorials or sample code - they have this covered extensively.
// create and set query for a user with a specific username
PFQuery *query = [PFUser query];
[query whereKey:#"username" equalTo:#"usernameYouWantToAdd"];
// perform the query to find the user asynchronously
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (error) {
NSLog(#"Error: %# %#", error, [error userInfo]);
// the Parse error code for "no such user" is 101
if (error.code == 101) {
NSLog(#"No such user");
}
}
else {
// create a PFUser with the object received from the query
PFUser *user = (PFUser *)object;
[friendsRelation addObject:user];
[self.friends addObject:user];
// save the added relation in the Parse database
[self.currentUser saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error) {
NSLog(#" %# %#", error, [error userInfo]);
}
}];
}
}];
Note that referencing self inside the block can lead to a retain cycle and thus a memory leak. To prevent this, you can get create a weak reference to self outside the block, where ClassOfSelf is the class of what self is, in this case most likely your view controller:
__weak ClassOfSelf *weakSelf = self;
And then use it to access self in the block, for example:
[weakSelf.friends addObject:user];
First, to add a relation I would first get the relation column from the user, addObject on that relation, then just save the user.
Next, what kind of query are you looking for? Do you want to query for the friends of a user? Query the user table for users who are friends with so and so? Could you elaborate?

Slow download with Apple sample code + progress

I have implemented the following methods from the Apple site, available on this page:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/Tasks/UsingNSURLDownload.html
//on my .h file:
#interface AppDelegate : NSObject <NSApplicationDelegate, NSWindowDelegate, NSURLDownloadDelegate>
{
BOOL allJobDone;
#private
NSURLResponse*downloadResponse;
long long bytesReceived;
}
//on my .m file:
#implementation AppDelegate
#synthesize downloadResponse = _downloadResponse;
#synthesize bytesReceived = _bytesReceived;
//.... the rest..
- (IBAction)startProcess:(id)sender {
// some code here..
[self startDownloadingURL];
}
// start below with the Apple code available here: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/Tasks/UsingNSURLDownload.html
- (void)startDownloadingURL /*:sender*/
{
// Create the request.
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://freefr.dl.sourceforge.net/project/hpc/hpc/g95/gfortran-4.9-bin.tar.gz"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:30.0];
// Create the download with the request and start loading the data.
NSURLDownload *theDownload = [[NSURLDownload alloc] initWithRequest:theRequest delegate:self];
if (!theDownload) {
// Inform the user that the download failed.
NSLog(#"Download NOT started");
} else {
NSLog(#"Download started");
}
}
- (void)download:(NSURLDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename
{
NSString *destinationFilename;
destinationFilename = [[[_homeDirectory stringByAppendingPathComponent:#"Desktop"] stringByAppendingPathComponent:#"DOWN"] stringByAppendingPathComponent:filename];
[download setDestination:destinationFilename allowOverwrite:YES];
}
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
{
// Dispose of any references to the download object
// that your app might keep.
// Inform the user.
NSLog(#"Download failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)downloadDidFinish:(NSURLDownload *)download
{
// Dispose of any references to the download object
// that your app might keep.
// Do something with the data.
NSLog(#"%#",#"downloadDidFinish");
}
- (void)setDownloadResponse:(NSURLResponse *)aDownloadResponse
{
NSLog(#"aDownloadResponse - %#",aDownloadResponse);
downloadResponse = aDownloadResponse;
NSLog(#"downloadResponse - %#",downloadResponse);
}
- (void)download:(NSURLDownload *)download didReceiveResponse:(NSURLResponse *)response
{
// Reset the progress, this might be called multiple times.
// bytesReceived is an instance variable defined elsewhere.
bytesReceived = 0;
// Store the response to use later.
[self setDownloadResponse:response];
}
- (void)download:(NSURLDownload *)download didReceiveDataOfLength:(unsigned long)length
{
long long expectedLength = [downloadResponse expectedContentLength];
bytesReceived = bytesReceived + length;
if (expectedLength != NSURLResponseUnknownLength) {
// If the expected content length is
// available, display percent complete.
float percentComplete = (bytesReceived/(float)expectedLength)*100.0;
NSLog(#"Percent complete - %f",percentComplete);
} else {
// If the expected content length is
// unknown, just log the progress.
NSLog(#"Bytes received - %lld",bytesReceived);
}
}
Everything seems to work, but the download is really slow. Trying the link in Safari, everything is very fast.
I get the impression that part of the code which calculates the progress (I will need for the progress indicator), has to do with the slowdown.
Does anyone know how to fix speed problems?
After countless attempts, since everything looked ok before, these two simple lines added:
NSString* userAgent = #"user";
[theRequest addValue:userAgent forHTTPHeaderField:#"User-Agent"];
they have speed up the download in a truly surprising. Perhaps even more things needs to be added, but now it's really satisfying.

Completion Blocks ? Asynchronous processes embedded in synchronous workflow

Long time lurker, first time poster. I'm relatively new to objective-C so my apologies if I'm asking something fairly simple. My google & stack overflow-fu has let me down here, so I figured somebody could maybe help.
I have a synchronous process executing, say, three functions in a row - call it A -> B-> C , where task A executes, followed by B, followed by C.
Now, B involves an asynchronous process with a delegate callback for completion. But B must complete before C is executed, so I need some mechanism such that C is not triggered before B has finished. I imagine there must be a common design pattern for this problem?
Initially naive solution would be -
execute A
execute B
while (!B finished) {}
execute C
...but this seems really lame.
I suspect I can do this with some kind of block, but for the life of me I just can't figure it out. Could anyone help?
appreciate any assistance!
Guillaume
Thanks for all the feeback - apologies for not responding sooner. I've now resolved this in a slightly different way to the suggestions:
Firstly, I extended NSObject to have the following method -
#import "NSObject+LTExtensions.h"
#implementation NSObject (Testing)
- (void) performSelectorWithBlock: (SEL) selector withSemaphore:(dispatch_semaphore_t)semaphore
{
[self performSelector:selector]; // This selector should complete the semaphore
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
}
#end
This allows me to execute a block via a selector. When the block executes, the thread on which it is executed will wait until signaled to proceed by a specific dispatch semaphore.
What we can then do is as follows:
Call A
Create a dispatch semaphore and define a selector which executes B
Call the method defined above to execute B and wait for the selector to complete
When B is completed (via a delegate callback), it signals the dispatch semaphore to suspend the wait
I then execute C
So we have
A
B -> Asynchronous with delegate callback
C
Here's a simple example of how the above is implemented
-(void) methodA {
// ... do something
// Assign your semaphore (this is a dispatch_semaphore_t)
self.semaphore = dispatch_semaphore_create(0);
[self performSelectorWithBlock:#selector(methodB) withSemaphore:semaphore];
[self methodC];
}
-(void) methodB {
// ... do whatever needs to be done asynchronously
CFRunLoopRun();
}
-(void) methodBDelegateCallBack {
// This is called when B completes
// Signal completion
dispatch_semaphore_signal(self.semaphore);
CFRunLoopStop(CFRunLoopGetCurrent());
}
-(void) methodC {
...
}
Works very well without any issues (but I am new to Obj C, so there may be glaring issues with my approach).
Another approach to this problem might be the following: create an helper object for the async task and copy a completion block when the task is called. Call the completion block using the delegate methods once the async task is finished. As a result we might execute the tasks in order like the following:
FSTask *taskA = [FSTask taskWithName:#"Task A"];
FSAsyncTask *taskB = [FSAsyncTask asyncTaskWithName:#"Task B"];
FSTask *taskC = [FSTask taskWithName:#"Task C"];
[taskA performTaskWithCompletionBlock:^ (NSString *result) {
NSLog(#"%#", result);
[taskB performTaskWithCompletionBlock:^ (NSString *result) {
NSLog(#"%#", result);
[taskC performTaskWithCompletionBlock:^ (NSString *result) {
NSLog(#"%#", result);
}];
}];
}];
So how is this achieved? Well, look at the task objects below ...
FSTask.m - synchronous work on main thread ...
#interface FSTask ()
#property (nonatomic, copy) NSString *name;
#end
#implementation FSTask
#synthesize name = _name;
+ (FSTask *)taskWithName:(NSString *)name
{
FSTask *task = [[FSTask alloc] init];
if (task)
{
task.name = name;
}
return task;
}
- (void)performTaskWithCompletionBlock:(void (^)(NSString *taskResult))block
{
NSString *message = [NSString stringWithFormat:#"%#: doing work on main thread ...", _name];
NSLog(#"%#", message);
if (block)
{
NSString *result = [NSString stringWithFormat:#"%#: result", _name];
block(result);
}
}
#end
FSAsyncTask.m - asynchronous work on background thread ...
#interface FSAsyncTask ()
#property (nonatomic, copy) void (^block)(NSString *taskResult);
#property (nonatomic, copy) NSString *name;
- (void)performAsyncTask;
#end
#implementation FSAsyncTask
#synthesize block = _block;
#synthesize name = _name;
+ (FSAsyncTask *)asyncTaskWithName:(NSString *)name
{
FSAsyncTask *task = [[FSAsyncTask alloc] init];
if (task)
{
task.name = name;
}
return task;
}
- (void)performTaskWithCompletionBlock:(void (^)(NSString *taskResult))block
{
self.block = block;
// the call below could be e.g. a NSURLConnection that's being opened,
// in this case a NSURLConnectionDelegate method will return the result
// in this delegate method the completion block could be called ...
dispatch_queue_t queue = dispatch_queue_create("com.example.asynctask", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^ {
[self performAsyncTask];
});
}
#pragma mark - Private
- (void)performAsyncTask
{
for (int i = 0; i < 5; i++)
{
NSString *message = [NSString stringWithFormat:#"%d - %#: doing work on background thread ...", i, _name];
NSLog(#"%#", message);
[NSThread sleepForTimeInterval:1];
}
// this completion block might be called from your delegate methods ...
if (_block)
{
dispatch_async(dispatch_get_main_queue(), ^ {
NSString *result = [NSString stringWithFormat:#"%#: result", _name];
_block(result);
});
}
}
#end
You can assign a block property to B where it would be used to execute a block of code before calling the delegate method. something like:
#property (nonatomic, copy)void(^yourBlock)(id blockParameter);
So, after calling B's delegate, you could call upon this block and execute it. Inside this block, you can call C's method.
the way I handled this is.
I created a NSMutableDictionary before the async call.
Then i make the async call. and do a check for the value I am waiting for
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[AsyncCallClass asyncCall:^{
#synchronized(dictionary) {
[dictionary setValue:myValue forKey:#"result"];
}
}];
while (true){
#synchronized(dictionary){
if ([dictionary valueForKey:#"resultValue"] != nil){
break;
}
}
[NSThread sleepForTimeInterval:.25];
}
MyResultClass *result = [dictionary valueForKey:#"resultValue"];
you can add time out for this too to stop it from being an infinite loop. but this is my solution. and it seems to work pretty well.
Here is the typical code I use to do such things (adapt the completionBlock signature and method names to your needs of course)
typedef void (^BCompletionBlock)(void);
#interface B : NSObject <BDelegate>
#property(nonatomic, copy) BCompletionBlock completionBlock;
-(void)doAsynchronousActionWithCompletion:(BCompletionBlock)aCompletionBlock;
#end
#implementation B
-(void)doAsynchronousActionWithCompletion:(BCompletionBlock)aCompletionBlock
{
// Store the completion block for later use
self.completionBlock = aCompletionBlock;
// Then execute your asynchronous action, that will call some delegate method when done
[self doYourAsynchronousActionWithDelegate:self];
}
-(void)yourBDelegateMethodCalledWhenDone
{
// Upon your async task completion, call your completion block then
if (self.completionBlock) self.completionBlock();
}
#end
Then here is an example usage:
-(void)doActions
{
[a doSynchronousAction];
[b doAsynchronousActionWithCompletion:^{
[c doSynchronousAction];
// A,B,C are now done
}];
}
I do this quite all the time to "convert" actions that uses delegate methods (to tell me when they are done) to actions that uses completionBlocks (have some classes to do this for UIAlertViews, UIActionsSheets, and many more cases for example) and it works like a charm.
I find it much more easier to use completionBlocks than the delegate mechanism in such cases.
You can also pass C in a block like so...
define a custom block
typedef void(^myCompletion)(BOOL complete);
Create your B method
-(void)performBWithCompletionBlock:(myCompletion)complete;
{
// do your things
[self.delegate delegateCallback];
complete(YES);
}
then create BG / async ABC
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // now we're on a BG queue to perform our async tasks
[self performA];
[self performBWithCompletionBlock:^(BOOL complete) {
if (complete == YES)
[self performC];
}];
});
If you want C to be on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self performC];
});

CLGeocoder reverseGeocodeLocation. First time in [placemarks count = 0]?

I am new to ObjC and I am struggling with the CLGeocoder. I want to be able to use reverseGeocodeLocation to obtain a string that contains the user location that I pass to my delegate when the user presses a Done button.
So the user triggers the display of a MapViewController, I call the reverseGeocodeLocation in the viewDidLoad but the [placemarks count = 0] this first time in, and I have no placemark to get the info that I need. The second time the user triggers the display of the MapViewController the placemarks array has been populated and everything works.
I suspect it is something to do with the reverseGeocodeLocation being an asynchronous call - but I cannot figure out how to solve this problem. I have tried searching online but nothing seems to help me understand what I am doing wrong and how i can solve this issue. Thanks in advance.
#interface MapViewController ()
#property (strong, nonatomic) CLGeocoder *geocoder;
#property (readwrite, nonatomic) NSString *theLocationName;
#end
#implementation MapViewController
#synthesize mapView, geocoder, delegate = _delegate, theLocationName = _theLocationName;
- (void)viewDidLoad
{
[super viewDidLoad];
self.mapView.delegate=self;
self.mapView.showsUserLocation = YES;
[self theUserLocation];
}
-(void)theUserLocation
{
if (!geocoder)
{
geocoder = [[CLGeocoder alloc] init];
}
MKUserLocation *theLocation;
theLocation = [self.mapView userLocation];
[geocoder reverseGeocodeLocation:theLocation.location
completionHandler:^(NSArray* placemarks, NSError* error)
{
if ([placemarks count] > 0)
{
CLPlacemark *placemark = [placemarks objectAtIndex:0];
[self setTheLocationName: placemark.locality];
}
}];
- (IBAction)done:(id)sender
{
[[self delegate] mapViewControllerDidFinish:self locationName:[self theLocationName]];
}
#end
This is not exact answer to your question but, if you can switch to other solution apart from CLGeocoder than following function can help you to get address from given latitude, longitude
#define kGeoCodingString #"http://maps.google.com/maps/geo?q=%f,%f&output=csv" //define this at top
-(NSString *)getAddressFromLatLon:(double)pdblLatitude withLongitude:(double)pdblLongitude
{
NSString *urlString = [NSString stringWithFormat:kGeoCodingString,pdblLatitude, pdblLongitude];
NSError* error;
NSString *locationString = [NSString stringWithContentsOfURL:[NSURL URLWithString:urlString] encoding:NSASCIIStringEncoding error:&error];
locationString = [locationString stringByReplacingOccurrencesOfString:#"\"" withString:#""];
return [locationString substringFromIndex:6];
}
Credit : Selected Answer to this question
So the user triggers the display of a MapViewController, I call the reverseGeocodeLocation in the viewDidLoad but the [placemarks count = 0] this first time in, and I have no placemark to get the info that I need. The second time the user triggers the display of the MapViewController the placemarks array has been populated and everything works.
It's not because the call is asynchronous - it's because the first time you call theUserLocation the actual location isn't available. Getting the user's location is not instantaneous - it takes time. However, you're asking for the user's location as soon as the map loads, which in most circumstances won't work.
What you need to do is hook into the MKMapViewDelegate methods, which provide you with callbacks when the location is updated. You can use this to check the location's accuracy, and decide whether it is accurate enough for you to reverse geolocate.