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

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.

Related

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?

Add shareToSms function to Cordova ShareKit Plugin

I would like to implement a function to directly share to Sms in the ShareKit Plugin. It have 3 builted-in function to share directly to Facebook, Mail and Twitter. I took a look in plugin code and it seems to me pretty easy, taking as example the function
shareToMail(subject,body);
- (void)shareToMail:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options {
[SHK setRootViewController:self.viewController];
SHKItem *item;
NSString *subject = [arguments objectAtIndex:1];
NSString *body = [arguments objectAtIndex:2];
item = [SHKItem text:body];
item.title = subject;
[SHKMail shareItem:item];
}
It seems that it takes as input the two parameters passed in (objectAtIndex:1 and objectAtIndex:2) and then it assign them to the item object (item = [SHKItem text:body];) for send it to SHKMail method.
That is what I understood, but I'm really a noob in ObjC, so... can someone give me some advice in how to create a function that call the Sms method? I think it is called SHKTextMessage but once again I'm really not sure about this...
if someone need this...
In ShareKitPlugin.m add this:
//at the top, near the other import
#import "SHKTextMessage.h"
//somewhere between #implementation ShareKitPlugin and #end
- (void)shareToSms:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options {
[SHK setRootViewController:self.viewController];
SHKItem *item;
NSString *message = [arguments objectAtIndex:1];
item = [SHKItem text:message];
[SHKTextMessage shareItem:item];
}
In the ShareKitPlugin.js add:
ShareKitPlugin.prototype.shareToSMS = function( message)
{
cordova.exec(null, null, "ShareKitPlugin", "shareToSms", [message] );
};
And now you can share trought Sms using this function:
window.plugins.shareKit.shareToSMS(message);

UIAlertView showing twice

I have an application that, in part, loops through the contents of an NSSet and displays a UIAlertView for each item found in the set. When there is only one item in the set, the UIAlertView behaves itself properly. However, if there's more than one, the first view flashes up (normally with the contents of the last item in the set) and then disappears without any user intervention. The first item in the NSSet will then display and wait for a response, before displaying the next item in the NSSet and so on.
It is the same experience as is being described in this unresolved question: IPHONE: UIAlertView called twice in a custom function/IBAction
Here's the code:
#import "CalcViewController.h"
#interface CalcViewController()
#property (nonatomic) int variablesCount;
#property (nonatomic, strong) NSMutableDictionary *variablesSet;
#end
#implementation CalcViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.variablesSet = [[NSMutableDictionary alloc] init];
}
- (IBAction)variablePressed:(UIButton *)sender
{
[[self calcModel] setVariableAsOperand:sender.titleLabel.text];
self.expressionDisplay.text = [[self calcModel] descriptionOfExpression:self.calcModel.expression];
}
- (IBAction)solveExpressionPressed:(UIButton *)sender {
self.variablesCount = 0;
[self.variablesSet removeAllObjects];
NSSet *variablesCurrentlyInExpression = [[NSSet alloc] initWithSet:[CalcModel variablesInExpression:self.calcModel.expression]];
self.variablesCount = [variablesCurrentlyInExpression count];
if (variablesCurrentlyInExpression){
for (NSString *item in variablesCurrentlyInExpression) {
UIAlertView *alertDialog;
alertDialog = [[UIAlertView alloc] initWithTitle:#"Enter value for variable"
message:item
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
alertDialog.alertViewStyle=UIAlertViewStylePlainTextInput;
UITextField * alertTextField = [alertDialog textFieldAtIndex:0];
alertTextField.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
[alertDialog show];
}
}
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (buttonIndex == 0){
if ([[alertView textFieldAtIndex:0] text]){
self.variablesSet[alertView.message] = [[alertView textFieldAtIndex:0] text];
}
}
if ([self.variablesSet count] == self.variablesCount){
NSLog(#"time to solve");
[[self calcDisplay] setText:[NSString stringWithFormat:#"%g", [CalcModel evaluateExpression:self.calcModel.expression usingVariableValues:self.variablesSet]]];
}
}
I've checked the IBActions behind the button that triggers the solveExpressionPressed method and that is the only one that exists. I've also placed some logging before the [alertDialog show]; line and it is only called twice when the variablesCurrentlyInExpression NSSet contains two values, yet the UIAlertView appears three times (flashing up once).
Finally, i've tried it without the following code:
UITextField * alertTextField = [alertDialog textFieldAtIndex:0];
alertTextField.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
and the problem still occurs, so I don't think it's that.
I've been stuck on this a while and haven't figured it out (hence the post!!), so any help would be greatly appreciated.
Thanks
Try showing the first UIAlertView and then showing the second after the first is dismissed.
What's happens is if an app or the OS calls [alert show] and a UIAlertView is already being displayed, the original alertView gets placed in a queue and the new one is presented. When the new one is dismissed, the original UIAlertView is re-shown.
Hope this helps
Easily fixed with a boolean flag that you set to YES when the first alert is shown. Then when the second match is found and the boolean is already YES because the alert is visible you won't show it. Then again you might want to know the exact amount of matches in the NSSet. In that case you keep track with a counter and show the alert after the match function has been done and the counter is not 0.
Avoid showing the alert inside the method of the button trigger. Instead split every function into different sets of methods. Not just for making your function work but maintainability of the code later.
To get this done, you'll need to keep some extra state in your class, like this...
#property (strong, nonatomic) NSMutableSet *promptVariables;
#property (strong, nonatomic) NSString *promptVariable;
#property (strong, nonatomic) NSMutableDictionary *promptResults;
You can probably get away with less by keeping some in your model as it is (or hiding a little in the alert view message as you cleverly do currently), but I'll use all new variables for clarity.
When you want to make several prompts, set up your state like this...
self.promptVariables = [[NSSet alloc] initWithSet:[CalcModel variablesInExpression:self.calcModel.expression]];
[self promptForVariables];
Define promptForVariables to bail if it has no work to do (promptVariables is empty) or remove one and do the alert for it.
- (void)promptForVariables {
if (![self.promptVariables count]) return;
self.promptResults = [NSMutableDictionary dictionary];
self.promptVariable = [self.promptVariables anyObject];
[self.promptVariables removeObject:self.promptVariable];
// do your alert here, I won't repeat your code
}
Then when the alert is done, process the result as you have and call promptForVariables again. The next time, since you've changed state, it has less work to do.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (buttonIndex == 0){
if ([[alertView textFieldAtIndex:0] text]){
[self.promptResults setValue:[[alertView textFieldAtIndex:0] text] forKey:self.promptVariable];
}
[self performSelector:#selector(promptForVariables) withObject:nil afterDelay:0.0];
}
}
When this is done, promptResults will contain variable names as keys and user inputs as values.

Objects string value is not set correctly

I'm writing a small project time management program for myself and have run into a problem which has confounded me.
They way it's set up is that I have an object called TCObject which I use in another object called ProjectTimerController. ProjectTimerController is a NSWindowController and it has it's own NIB file.
What I'm doing is pretty straight forward. When you click a line in a NSTableView ProjectTimerController finds a TCObject which corresponds to that line. It then loads info from that TCObject into an interface where you can view and edit some stuff.
Here's a screenshot of what it looks like:
Now when I change the text in NSTextView and then press the Add button the -saveString function is called and currentSelection (which is a TCObject and represents the currently selected line) and it's notes variable is set. I know that _notes is set as the new value as NSLog function logs the correct string being in _notes when setString is run. The same, correct, string is logged in -tableViewSelectionDidChange: just before currentSelection is set as the newly selected object.
But if I select the line where I just changed the notes it just loads the same text, "Initial String" and checking _notes tells me it's "Initial String".
Thing I don't have this problem with isFinished. When the Finished check box is toggled I set the corresponding TCObjects' isFinished Boolean value to the same value as the checkbox. This the object remembers and correctly changes depending on what line I have selected.
[EDIT]
*I've added a clearer explanation here.
I click a line in the NSTableView (lets say the top one)
This loads a corresponding TCObject from the myProjects array and that object's variable are added to the Notes NSTextView box and Finished is toggled on or off.
If I now write into The Notes box and press "Add" the text there is set into that TCObject's _notes variable.
So If I click another line some other text is loaded into the Notes box. Clicking back on the top line should give me the string I just wrote into Notes in step 3. But it doesn't. _notes always seems to contain the string I set when I initialize it in the -init method.
The "Finished" checkbox works fine. When I click that the state is saved and loaded correctly when I click a line.
I know that _notes is correctly set when I press the Add button as the NSLog method in setString logs the string I have written into Notes when I press the Add button.
[/EDIT]
Here below is a barebones version of TCObject and ProjectTimerController.
//TCObject.h
#interface TCObject : NSObject
{
NSString *_notes;
Boolean _isFinished;
}
#property (retain, nonatomic) NSString *notes;
#property (nonatomic) Boolean isFinished;
#end
//TCObject.m
#import "TCObject.h"
#implementation TCObject
#synthesize notes = _notes, isFinished = _isFinished;
-(id)init{
if (self = [super init]) {
self.notes = #"Initial string";
self.isFinished = NO;
}
return self;
}
- (void)dealloc {
[_notes release]; _notes = nil;
[super dealloc];
}
-(void)setNotes:(NSString *)notes {
[notes retain];
[_notes release];
_notes = notes;
NSLog(#"Setting _notes as: %#", _notes);
}
-(NSString *)notes {
NSLog(#"Getting _notes, which is: %#", _notes);
return _notes;
}
#end
//ProjectTimerController.m
- (id)initWithWindow:(NSWindow *)window {
self = [super initWithWindow:window];
if (self)
{
myProjects = [[NSMutableArray alloc]init];
currentSelection = nil;
TCObject *newProject = [[TCObject alloc] init];
TCObject *newProject2 = [[TCObject alloc] init];
TCObject *newProject3 = [[TCObject alloc] init];
TCObject *newProject4 = [[TCObject alloc] init];
[myProjects addObject:newProject];
[myProjects addObject:newProject2];
[myProjects addObject:newProject3];
[myProjects addObject:newProject4];
}
return self;
}
-(IBAction)isFinishedToggle:(id)sender {
if(currentSelection != nil){
currentSelection.isFinished = finishedCheckBtn.state;
}
}
-(IBAction)saveString:(id)sender {
if(currentSelection != nil){
currentSelection.notes = [[notesField textStorage] string];
}
}
//delegate function for NSTableView
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification {
NSInteger selectedIndex = [table selectedRow];
if(selectedIndex == -1){
return;
}
//here the correct notes string is printed
NSLog(#"curr: %i", currentSelection.notes);
currentSelection = [myProjects objectAtIndex:selectedIndex];
NSString *notesInfo = currentSelection.notes;
Boolean isFinishedInfo = currentSelection.isFinished;
[notesField setString:notesInfo];
[finishedCheckBtn setState:isFinishedInfo];
}
Finally found the problem. Seems that changing notes in this way:
-(IBAction)saveString:(id)sender {
if(currentSelection != nil){
currentSelection.notes = [[notesField textStorage] string];
}
}
causes some problems. Everything works fine if I do it this way:
-(IBAction)saveString:(id)sender{
if(currentSelection != nil){
NSString *temps = [NSString stringWithFormat:#"%#", [[notesField textStorage] string]];
currentSelection.notes = temps;
}
}
So I'm guessing what was going on is that _notes was pointing to the text contained in my NSTextView. So when I changed the text there _notes also changed or something like that...

Need to retain a CFSocketRef?

I am facing a problem: I have two view controllers, viewController1, and viewController2.
Here is the method I'm concerned about in viewController1:
-(void)msgToServer:(NSString*)identifier:(NSString *)_username{
NSString *message = [NSString stringWithFormat:#"%#|%#|<END>", identifier, _username];
CFDataRef messageData = (CFDataRef)[message dataUsingEncoding:NSUTF8StringEncoding];
CFSocketSendData(s, NULL, messageData, 0);
}
this method is working perfectly in viewController1.
Now I am calling this method from viewController2. I am passing two arguments, identifier and _username. It generates the string perfectly, but when it reaches the last line it misses the value of s -- which is a CFSocketRef.
What should I do so that s will retain its value, even if I am in viewController2?
Code from viewController2 which calls the above method:
- (void)viewDidLoad {
[super viewDidLoad];
WatchListViewController *watchListViewController = [[WatchListViewController alloc]init];
[watchListViewController msgToServer:#"PREQ" :userName];
}
Thanks in advance.
you could use the retain function of NSObject.
- (id)retain
See in Apple Documentation
Also have a look to Object Ownership and Disposal in Memory Management Programming Guide
EDITED:
Try with below code
You might have issue with function prototype,not with retaining.
use below
-(void)msgToServer:(NSString*)identifier withUsername:(NSString *)_username;.
- (void)viewDidLoad {
[super viewDidLoad];
WatchListViewController *watchListViewController = [[WatchListViewController alloc]init];
[watchListViewController msgToServer:#"PREQ" withUsername:userName];
}
-(void)msgToServer:(NSString*)identifier withUsername:(NSString *)_username{
NSString *message = [NSString stringWithFormat:#"%#|%#|<END>", identifier, _username];
CFDataRef messageData = (CFDataRef)[message dataUsingEncoding:NSUTF8StringEncoding];
CFSocketSendData(s, NULL, messageData, 0);
}
Thanx to all who helped me well i solved this problem in this way:
when I push from viewController1 to viewController2 i am passing two parameters "username" and "s" to viewController2 and then I write this method in viewController2.
-(void)msgToServer:(NSString*)identifier:(NSString *)_username{
NSString *message = [NSString stringWithFormat:#"%#|%#|<END>", identifier, _username];
CFDataRef messageData = (CFDataRef)[message dataUsingEncoding:NSUTF8StringEncoding];
CFSocketSendData(s, NULL, messageData, 0);
}
then I am calling this method in the same view controller and it works perfectly. I think this is the only solution which i find. if any body can make it more efficient then you are welcome. :)