Passing CLGeocoder city name with delegate protocol - objective-c

I´m trying to pass the placemark locality from CLGeocoder to another class UILabel named cityLabel. The class with CLGeocoder named WXManager and the mainViewController named WXController. I have tried every way around to make it work, but I simply can´t get the city name passed to the cityLabel.
WXManager.h
#class WXManager;
#protocol WXManagerDelegate <NSObject>
- (void)WXManagerDidGeocodeCityName:(NSString *)cityName;
#end
#interface WXManager : NSObject <CLLocationManagerDelegate>
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;
#end
WXManager.m
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
if (self.isFirstUpdate) {
self.isFirstUpdate = NO;
return;
}
CLLocation *location = [locations lastObject];
if (location.horizontalAccuracy > 0) {
self.currentLocation = location;
[self.locationManager stopUpdatingLocation];}
CLGeocoder *fgeo = [[CLGeocoder alloc] init];
// Reverse Geocode a CLLocation to a CLPlacemark
[fgeo reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error){
if(!error){
for(CLPlacemark *placemark in placemarks){
self.cityName = [placemark locality];
[self.customDelegate WXManagerDidGeocodeCityName:self.cityName];
NSLog(#"city is %#",cityName); }
} else {
NSLog(#"There was a reverse geocoding error\n%#",
[error localizedDescription]);
}
}
];
}
WXController.h
#interface WXController : UIViewController
<WXManagerDelegate>
- (void)WXManagerDidGeocodeCityName:(NSString *)cityName;
#end
And at last in WXController.m where the problem seems to be, my method WXManagerDidGeocodeCityName wont get called. And I don´t know what´s wrong. Does anyone know how to make this method get called?
WXManager *cityName = [[WXManager alloc] init];
cityName.customDelegate = self;
#pragma mark - Custom Delegate Method
- (void)WXManagerDidGeocodeCityName:(NSString *)cityName
{
[self.cityLabel setText:cityName];
NSLog(#"cityName is %#", cityName);
NSLog(#"DidGetCalled");
}
The log doesnt get called so there´s got to be something missing somewhere.

Try this:
WXManager.h
#class WXManager;
#protocol WXManagerDelegate <NSObject>
- (void)WXManagerDidGeocodeCityName:(NSString *)cityName;
#end
#interface WXManager : NSObject <CLLocationManagerDelegate> {
id <WXManagerDelegate> customDelegate;
}
#property (retain)id customDelegate;
#end
WXController.h
#interface WXController : UIViewController <WXManagerDelegate>
//- (void)WXManagerDidGeocodeCityName:(NSString *)cityName;
#end

in WXManager.m you need initialize CLLocationManager:
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager startUpdatingLocation];
and everything will work out fine

I'm going to guess that cityName is weak, or has a custom setter/getter that clears/ignores the correct value.
Edit for clarification:
I was talking about your cityName here:
WXManager *cityName = [[WXManager alloc] init];
cityName.customDelegate = self;
I was assuming it would be assigned some property self.cityName later on.
If you don't, your self.customDelegate will become nil by the time the completion handler runs.

The method is not called? Or variable cityName equal nil?
If first - look for your delegate may be it equal nil.
If second - CLGeocoder work not correct.
Generally stroll debugger. Look at the variables. Quickly understand everything.

in WXManager.m you need initialize CLLocationManager:
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self; [self.locationManager
startUpdatingLocation];
and everything will work out fine

Related

Append Google Maps URL with Current Location

I am trying to compose an email with a Google Maps link using Core Location and MessageUI. Presently my code produces this string: https://maps.google.com?saddr=Current+Location&daddr=0.000000,0.000000.
I would like to append the device's longitude and latitude with the URL.
Here is my implementation:
#import "ViewController.h"
#interface ViewController () <CLLocationManagerDelegate>
#end
#implementation ViewController{
NSString *currentLongitude;
NSString *currentLatitude;
NSString *googleMapsURL;
CLLocationManager *locationManager_;
}
- (void)viewDidLoad {
[super viewDidLoad];
locationManager_ = [[CLLocationManager alloc] init];
locationManager_.delegate = self;
locationManager_.distanceFilter = kCLDistanceFilterNone;
locationManager_.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager_ requestAlwaysAuthorization];
[locationManager_ startUpdatingLocation];
}
- (IBAction)composeMailButton:(id)sender {
NSString *bodyHeader = #"Here are you directions:";
NSString *mailBody = [NSString stringWithFormat:#"%#\n%#", bodyHeader, googleMapsURL];
MFMailComposeViewController *emailComposer = [[MFMailComposeViewController alloc] init];
[emailComposer setSubject:#"Google Maps Directions"];
[emailComposer setMessageBody:mailBody isHTML:NO];
[emailComposer setToRecipients:#[#"castro.michael87#gmail.com"]];
[emailComposer setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[self presentViewController:emailComposer animated:YES completion:nil];
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
CLLocation *newLocation = [locations lastObject];
NSString *googleMapsURL = [[NSString alloc] initWithFormat:#"https://maps.google.com?saddr=Current+Location&daddr=%1.6f,%1.6f",newLocation.coordinate.latitude, newLocation.coordinate.longitude];
}
I am guessing that I have not correctly implemented the locationManager. Any input is very much appreciated!
First, you need to make use that you added the NSLocationWhenInUseUsageDescription and NSLocationAlwaysUsageDescription in your info.plist file.
Second, your viewController need to implement the <CLLocationManagerDelegate>
#interface ViewController ()<CLLocationManagerDelegate>
Third, setup your locationManager in your viewDidLoad method.
locationManager_ = [[CLLocationManager alloc] init];
locationManager_.delegate = self;
locationManager_.distanceFilter = kCLDistanceFilterNone;
locationManager_.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager_ requestAlwaysAuthorization];
[locationManager_ startUpdatingLocation];
Fourth, implement the -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations method:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
CLLocation *newLocation = [locations lastObject];
NSString *googleMapsURL = [[NSString alloc] initWithFormat:#"https://maps.google.com?saddr=Current+Location&daddr=%1.6f,%1.6f",newLocation.coordinate.latitude, newLocation.coordinate.longitude];
NSLog(#"%#", googleMapsURL);
}
Finally, if you test in simulator, you need to simulate a location:
Code Snippet: https://gist.github.com/ziyang0621/b1be760596da54873f81

Current Latitude Longitude on Google Map

I am using below code to get current lat long in iOS 8.
.h
#interface DirectionViewController : UIViewController<CLLocationManagerDelegate,GMSMapViewDelegate>
{
CLLocationManager *locationManager;
}
#property (nonatomic, retain) CLLocationManager *locationManager;
.m
#synthesize locationManager;
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.distanceFilter = kCLDistanceFilterNone;
[locationManager startUpdatingLocation];
CLLocation *location = [locationManager location];
// Configure the new event with information from the location
CLLocationCoordinate2D coordinate = [location coordinate];
NSString *latitude = [NSString stringWithFormat:#"%f", coordinate.latitude];
NSString *longitude = [NSString stringWithFormat:#"%f", coordinate.longitude];
NSLog(#"dLatitude : %#", latitude);
NSLog(#"dLongitude : %#",longitude);
But here, I am getting 0.00000 value for both latitude and longitude.
Can anybody help me here. How to get current lat long in iOS 8?
Thanks in advance!
AppDelegte.h
#interface AppDelegate : UIResponder <UIApplicationDelegate,CLLocationManagerDelegate>
{
CLLocation *currentLocation;
}
#property (strong, nonatomic) CLLocationManager *locationManager;
#property (nonatomic, assign) CLLocationCoordinate2D currentLocationCoordinate;
Appdelget.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
locationManager = [[CLLocationManager alloc]init]; // initializing locationManager
locationManager.delegate = self; // we set the delegate of locationManager to self.
locationManager.desiredAccuracy = kCLLocationAccuracyBest; // setting the accuracy
[locationManager startUpdatingLocation];
}
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
CurrentLatitude=newLocation.coordinate.latitude;
CurrentLongitude=newLocation.coordinate.longitude;
NSLog(#"%f",CurrentLatitude);
NSLog(#"%f",CurrentLongitude);
[locationManager stopUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
NSLog(#"didFailWithError: %#", error);
UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Failed to Get Your Location" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[errorAlert show];
}
.Plsit add
NSLocationAlwaysUsageDescription
NSLocationWhenInUseUsageDescription
You're not using the proper method to get your coordinates.
once you call startUpdatingLocation, it calls the location manager methods (that you must implement in order for this to work).
There are two of them, [locationManager:didFailWithError:][1]
and [-locationManager:didUpdateLocations:][2]. You should have warnings asking you to add those methods in your .m, with the proper spelling if you haven't done it yet.
didUpdateLocation is called every X seconds and returns coordinates. You have to build your coordinate strings in this very specific method, where the coordinates are set.
From what I could gather,
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations
Will give you an array of CLLocation objects containing the location data.
This array always contains at least one object representing the current location.
If updates were deferred or if multiple locations arrived before they could be delivered, the array may contain additional entries.
The objects in the array are organized in the order in which they occurred. Therefore, the most recent location update is at the end of the array.
This means you'll have to get the lastObject of that array for the most recent location.
Here is an example i've found on the web, for what the inside of that method could look. :
CLLocation *newLocation = [locations lastObject];
CLLocation *oldLocation;
if (locations.count > 1) {
oldLocation = [locations objectAtIndex:locations.count-2];
} else {
oldLocation = nil;
}
NSLog(#"didUpdateToLocation %# from %#", newLocation, oldLocation);
MKCoordinateRegion userLocation = MKCoordinateRegionMakeWithDistance(newLocation.coordinate, 1500.0, 1500.0);
[regionsMapView setRegion:userLocation animated:YES];
Make sure to ask for the different permissions beforehand, and that you have them.
Also note that this method is only available since iOS5 and is different in the previous versions. Though it is very unlikely that you still support iOS5, I thought I should mention it.

Archiving and Unarchiving data/model objects

I'm following the MVCS paradigm for saving and then loading instances of data objects in a Test app related to updating mapping data.
BNRMapPoint.h
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
#interface BNRMapPoint : NSObject<MKAnnotation, NSCoding>
{
double latitude;
double longitude;
#public NSArray *mapPoints;
}
-(id) initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t subTitle:(NSString *)st;
//this is required property in MKAnnotation Protocol
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
//this is an optional property in MKAnnotation Protocol
#property (nonatomic, copy) NSString *title;
//this is an optional property in MKAnnotation Protocol
#property (nonatomic,readonly,copy) NSString *subTitle;
#end
BNRMapPoint.m (Model)
#import "BNRMapPoint.h"
#implementation BNRMapPoint
#synthesize coordinate=_coordinate;
#synthesize title=_title;
#synthesize subtitle=_subtitle;
-(void)encodeWithCoder:(NSCoder *)aCoder{
latitude = self.coordinate.latitude;
longitude = self.coordinate.longitude;
[aCoder encodeObject:self.title forKey:#"title"];
[aCoder encodeObject:_subTitle forKey:#"subTitle"];
[aCoder encodeDouble: latitude forKey:#"latitude"];
[aCoder encodeDouble:longitude forKey:#"longitude"];
}
-(id)initWithCoder:(NSCoder *)aDecoder{
if (self= [super init]) {
[self setTitle:[aDecoder decodeObjectForKey:#"title"]];
self->_subTitle= [aDecoder decodeObjectForKey:#"subTitle"];
latitude= [aDecoder decodeDoubleForKey:#"latitude"];
longitude= [aDecoder decodeDoubleForKey:#"longitude"];
}
return self;
}
-(id)initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t subTitle:(NSString *)st{
if (self= [super init]) {
self.coordinate=c;
self.title=t;
_subtitle=st;
}
return self;
}
-(id) init{
return [self initWithCoordinate:CLLocationCoordinate2DMake(43.07, -89.32) title:#"Hometown" subTitle:self.subtitle];
}
#end
WhereamiViewController.h
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>
#import "BNRMapPoint.h"
#import "RootObject.h"
#interface WhereamiViewController : UIViewController<CLLocationManagerDelegate,MKMapViewDelegate,UITextFieldDelegate>
{
#public RootObject *rootObj;
CLLocationManager *locationManager;
IBOutlet MKMapView *worldView;
IBOutlet UIActivityIndicatorView *activityIndicator;
IBOutlet UITextField *locationTitleField;
}
-(IBAction)buttonDidGetPressed:(id)sender;
-(BOOL)textFieldShouldReturn:(UITextField *)textField;
-(void)findLocation;
-(void)foundLocation:(CLLocation *)loc;
#end
WhereamiViewController.m (ViewController)
#import "WhereamiViewController.h"
#interface WhereamiViewController ()
#end
#implementation WhereamiViewController
-(IBAction)buttonDidGetPressed:(UISegmentedControl *)sender{//Silver challenge
NSLog(#"%#",NSStringFromSelector(_cmd));
if([sender selectedSegmentIndex]==0){
[worldView setMapType:MKMapTypeStandard];
}
else if([sender selectedSegmentIndex]==1){
[worldView setMapType:MKMapTypeHybrid];
}
else if([sender selectedSegmentIndex]==2){
[worldView setMapType:MKMapTypeSatellite];
}
}
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
NSLog(#"%#", NSStringFromSelector(_cmd));
if (self=[super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
rootObj= [[RootObject alloc] init];
locationManager= [[CLLocationManager alloc] init];
[locationManager setDelegate:self];//self is Whereamicontroller. The delegate pointer is of type id<CLLocationManagerDelegate> and is an ivar of CLLocationManager.
[locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
}
NSLog(#"test");
return self;
}
-(void) viewDidLoad{
// [worldView setMapType:MKMapTypeSatellite]; Bronze challenge
[worldView setShowsUserLocation:YES];
}
-(void)findLocation{
NSLog(#"%#",NSStringFromSelector(_cmd));
[locationManager startUpdatingLocation];//This calls locationManager:didUpdateLocations:
NSLog(#"location updated");
[activityIndicator startAnimating];
[locationTitleField setHidden:YES];
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
NSLog(#"%#",NSStringFromSelector(_cmd));
[self findLocation];
[textField resignFirstResponder];
return YES;
}
-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{
NSLog(#"%#",NSStringFromSelector(_cmd));
CLLocationCoordinate2D centerCoordinate= [[userLocation location] coordinate]; //get the coordinate of current location.
MKCoordinateSpan span= MKCoordinateSpanMake(250, 250);//Structure members
MKCoordinateRegion mapPortionToDisplay= MKCoordinateRegionMakeWithDistance(centerCoordinate, span.latitudeDelta, span.longitudeDelta);//span.latitudeDelta=250 and span.longitudeDelta=250
[worldView setRegion:mapPortionToDisplay animated:YES];
// [worldView setRegion:mapPortionToDisplay];
}
-(void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading{
NSLog(#"%#",NSStringFromSelector(_cmd));
NSLog(#"Heading %#",newHeading);
}
-(void)foundLocation:(CLLocation *)loc{
NSLog(#"%#",NSStringFromSelector(_cmd));
CLLocationCoordinate2D coord= [loc coordinate];
NSDateFormatter *formatter= [[NSDateFormatter alloc] init];
NSDate *currentDate= [[NSDate alloc] init];
[formatter setDefaultDate:currentDate];
BNRMapPoint *bmp= [[BNRMapPoint alloc] initWithCoordinate:coord title:[locationTitleField text] subTitle:[[formatter defaultDate] description]];
[rootObj.mapPoints addObject:bmp];
[worldView addAnnotation:bmp];
MKCoordinateRegion region= MKCoordinateRegionMakeWithDistance(coord,250,250);
[worldView setRegion:region animated:YES];
//RESET the UI
[locationTitleField setText:#" "];
[activityIndicator stopAnimating];
[locationTitleField setHidden:NO];
[locationManager stopUpdatingLocation];
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{ //CLLocationManagerDelegate method implementation
NSLog(#"%#", NSStringFromSelector(_cmd));
// NSTimeInterval t0=[[locations lastObject] timeIntervalSinceNow];
NSLog(#"%#",(CLLocation *)[locations lastObject]);
NSTimeInterval t= [[(CLLocation *)[locations lastObject] timestamp] timeIntervalSinceNow];
if (t<-180) {
return; //No op
}
[self foundLocation:[locations lastObject]];
}
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
NSLog(#"Could not find location: %#",error);//CLLocationManagerDelegate method implementation
}
#end
RootObject.h
#import <Foundation/Foundation.h>
#interface RootObject : NSObject
#property NSMutableArray *mapPoints;
-(BOOL)saveChanges;
-(NSString *)dataObjectArchivePath;
#end
RootObject.m (Store)
#import "RootObject.h"
#implementation RootObject
#synthesize mapPoints;
-(NSString *)dataObjectArchivePath{
NSArray *documentDirectories= NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory= [documentDirectories objectAtIndex:0];
NSString *finalPath= [documentDirectory stringByAppendingPathComponent:#"items.archive"];
return finalPath;
}
-(BOOL)saveChanges{
NSString *path= [self dataObjectArchivePath];
return [NSKeyedArchiver archiveRootObject:self.mapPoints toFile:path];writing to the file items.archive
}
-(id)init{
if (self=[super init]) {
NSString *path= [self dataObjectArchivePath];
self.mapPoints= [NSKeyedUnarchiver unarchiveObjectWithFile:path];//reading from the file items.archive
if (!self.mapPoints) {
self.mapPoints= [[NSMutableArray alloc] init];
}
}
return self;
}
#end
AppDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
NSLog(#"%#",NSStringFromSelector(_cmd));
BOOL success= [[BNRItemStore sharedStore] saveChanges];
if (success) {
NSLog(#"Saved all of the BNRItems");
}
else{
NSLog(#"Could not save any of the BNRItems");
}
}
In the above code, the saveChanges method is called from application: didEnterBackground:. So i haven't made a singleton object for the Store (RootObject). But i instantiated it in the designated initializer of viewController. The data to be archived is BNRMapPoint objects. On the entering background state, the code did save the objects successfully. However after the relaunching the app, the initWithCoder is where its getting stuck. The app crashes when it comes to reading the previously saved MapPoint objects. Pls help.. I've checked almost possible. Don't know what to do. I'm stuck.
So the problem is that you have:
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
And you are trying to use setter on read only property:
self.coordinate=c;
This is why it says that unrecognized selector is send - becuase it's readonly so setter doesn't exists. Using property this was is the same as calling:
[self setCoordinate:c]
Just make property assignable

Objective-c iOS - Can't receive delegate methods in my custom class

My custom class needs to receive the "didUpdateToLocation" CLLocationManagerDelegate method, however i can't seem to make the following code work.
Header file.
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>
#interface MyCurrentLocation : NSObject<MKAnnotation,CLLocationManagerDelegate,MKMapViewDelegate>
{
CLLocationCoordinate2D coordinate;
}
#property (nonatomic, assign) CLLocationCoordinate2D coordinate;
#property (nonatomic, copy) CLLocationManager *locationManager;
-(MyCurrentLocation *)init;
#end
Implementation file.
#import "MyCurrentLocation.h"
#implementation MyCurrentLocation
-(BLCurrentLocation *)init
{
self = [super init];
if (self) {
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.distanceFilter = kCLDistanceFilterNone;
_locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
[_locationManager startUpdatingLocation];
}
return self;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
NSLog(#"Did we receive a location?");
}
You have to respond to other delegate methods, such as the error callback. Also, check for the CLLocationManager's + (CLAuthorizationStatus)authorizationStatus class method to determine if you are able to use location services.
I don't know why but maybe you can try to implement this delegate method to see if location service cannot be registered.
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Please make sure Location Service is ON" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alertView show];
}

UIAlertView with a user supplied context and [self autorelease]

I have looked over some ideas for how to supply a context to a UIAlertView. The common answers are save it in a dictionary or subclass UIAlertView. I don't like the idea of saving the context in a dictionary, it's the wrong place for the data. Subclassing UIAlertView is not supported by Apple, so by my standard, is not a good solution.
I came up with an idea, but I'm not sure what to make of it. Create an instance of a context object that is the delegate of UIAlertView. The alert view context, in turn, has it's own delegate which is the view controller.
The trouble is releasing memory. I set alertView.delegate to nil and call [self autorelease] to free the context object in -alertView:didDismissWithButtonIndex:.
THE QUESTION IS: What problems am I causing myself? I have a suspicion that I'm setting myself up for a subtle memory error.
Here is the simple version which only supports -alertView:clickedButtonAtIndex:
Use
- (void)askUserIfTheyWantToSeeRemoteNotification:(NSDictionary *)userInfo
{
[[[[UIAlertView alloc] initWithTitle:[userInfo valueForKey:#"action"]
message:[userInfo valueForKeyPath:#"aps.alert"]
delegate:[[WantAlertViewContext alloc] initWithDelegate:self context:userInfo]
cancelButtonTitle:#"Dismiss"
otherButtonTitles:#"View", nil] autorelease] show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex withContext:(id)context
{
if (buttonIndex != alertView.cancelButtonIndex)
[self presentViewForRemoteNotification:context];
}
Interface
#protocol WantAlertViewContextDelegate <NSObject>
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex withContext:(id)context;
#end
#interface WantAlertViewContext : NSObject <UIAlertViewDelegate>
- (id)initWithDelegate:(id<WantAlertViewContextDelegate>)delegate context:(id)context;
#property (assign, nonatomic) id<WantAlertViewContextDelegate> delegate;
#property (retain, nonatomic) id context;
#end
Implementation
#implementation WantAlertViewContext
- (id)initWithDelegate:(id<WantAlertViewContextDelegate>)delegate context:(id)context
{
self = [super init];
if (self) {
_delegate = delegate;
_context = [context retain];
}
return self;
}
- (void)dealloc
{
[_context release];
[super dealloc];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
[self.delegate alertView:alertView clickedButtonAtIndex:buttonIndex withContext:self.context];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
alertView.delegate = nil;
[self autorelease];
}
#synthesize delegate = _delegate;
#synthesize context = _context;
#end
You can use the concept of associated objects. Using the functions objc_setAssociatedObject() and objc_getAssociatedObject(). You can use these properties to essentially add a new property, in your case to hold an NSDictionary, to an object through a category.
Here is an example of a UIAlertView category. These files should be compiled without ARC, -fno-objc-arc flag set if the project is using ARC.
UIAlertView+WithContext.h:
#import <UIKit/UIKit.h>
#interface UIAlertView (Context)
#property (nonatomic, copy) NSDictionary *userInfo;
#end
UIAlertView+WithContext.m:
#import "UIAlertView+WithContext.h"
// This enum is actually declared elseware
enum {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
#implementation UIAlertView (Context)
static char ContextPrivateKey;
-(void)setUserInfo:(NSDictionary *)userInfo{
objc_setAssociatedObject(self, &ContextPrivateKey, userInfo, 3);
}
-(NSDictionary *)userInfo{
return objc_getAssociatedObject(self, &ContextPrivateKey);
}
#end
This category is easily used.
SomeViewController.m: a UIAlertViewDelegate using ARC or not.
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Title" message:#"Message" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
alert.userInfo = [NSDictionary dictionaryWithObject:#"Hello" forKey:#"Greeting"];// autorelease if MRC
[alert show]; // release if MRC
}
-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex{
NSLog(#"userInfo:%#",alertView.userInfo);
}
When you press the alertview's OK button you will see:
userInfo:{
Greeting = Hello;
}
A couple of notes:
1) Make sure the association type matches the property declaration so things behave as expected.
2) You probably shouldn't use userInfo for the property/association since Apple may well decide to add a userInfo property to UIAlertView in the future.
Edit To address your concerns about your [self autorelease];
It is imperative that you balance your implicit alloc retain from this line: delegate:[[WantAlertViewContext alloc] initWithDelegate:self context:userInfo]. You achieve this balance by calling [self autorelease]; in the final UIAlertView delegate method.
Granted, this does feel wrong. Mostly because there is no way when looking at this that it doesn't at first blush look like memory mis-management. But there is one simple way to avoid this "controlled leak" API you are creating; Have the instance of WantAlertViewContext explicitly retain itself. For example:
-(id)initWithDelegate:(id<WantAlertViewContextDelegate>)delegate context:(id)context{
self = [super init];
if (self) {
_delegate = delegate;
_context = [context retain];
}
return [self retain]; // Explicitly retain self
}
-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex{
alertView.delegate = nil;
[self autorelease]; // Or just [self release]; doesn't make much difference at this point
}
Now your class has some internal harmony. I say some because this is still not perfect. For example, if an instance is never an alert-view delegate it will never be released. It is still just a "semi-controlled" memory leak.
Anyway, now your instantiation call can look more logical:
delegate:[[[WantAlertViewContext alloc] initWithDelegate:self context:userInfo] autorelease];
I think that this particular design pattern is fraught with danger. If you do end up using it keep a close eye on it.
I've come up with a simpler solution that may fit in some circumstances. Because you get the NSAlertView context when the delegate gets called, I use the actual address of the object to make a tag (NSString*) which I then use to store custom values in a global or object specific NSDictionary. Here is an example:
+(NSString*)GetTag:(id)ObjectIn
{
return [NSString stringWithFormat:#"Tag-%i",(int)ObjectIn];
}
In the Delegate:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString* MyID = [CommandManager GetTag:alertView];
[CurrentActiveAlerts removeObjectForKey:MyID];
}
Calling:
UIAlertView *myAlert = [[UIAlertView alloc] initWithTitle:title_text
message:#""
delegate:self
cancelButtonTitle:nil
otherButtonTitles:button_text ,nil];
CurrentActiveAlerts[[CommandManager GetTag:myAlert]] = CommandToRun; // Querky way to link NSDict to UIAlert, but the best I could think of
[myAlert show];
[myAlert release];
The keys will end up looking like "Tag-226811776". Hope this helps.