How to safely pass a context object in an UIAlertView delegate? - iphone

In my application I use a UIAlertView to display to the user a message and some options. Depending on the button pressed, I want the application to perform something on an object.
The sample code I use is...
-(void) showAlert: (id) ctx {
UIAlertView *baseAlert = [[UIAlertView alloc]
initWithTitle: title
message: msg
delegate:self
cancelButtonTitle: cancelButtonTitle
otherButtonTitles: buttonTitle1, buttonTitle2, nil];
//baseAlert.context = ctx;
[baseAlert show];
[baseAlert release];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) {
id context = ...;//alertView.context;
[self performSelectorOnMainThread:#selector(xxx:) withObject: context waitUntilDone: NO];
}
}
Is there any way to pass an object into the delegate as a context object? or maybe some other way?
I could add the property on the delegate but the same delegate object is being used by many different alert views. For this reason I would prefer a solution where the context object is attached to the UIAlertView instance and carried across to the delegate as part of the UIAlertView object.

I still think storing it locally is the best solution. Create a class local NSMutableDictionary variable to hold a map of context objects, store the context with UIAlertView as the key and the context as the value.
Then when the alert method is called just look into the dictionary to see which context object is related. If you don't want to use the whole Alert object as a key, you could use just the address of the UIAlertView object:
NSString *alertKey = [NSString stringWithFormat:#"%x", baseAlert];
The address should be constant on the phone. Or you could tag each alert as the other poster suggested and use the tag to look up a context in the map.
Don't forget to clear out the context object when you are done!

A complete implementation that allows you to pass context:
#interface TDAlertView : UIAlertView
#property (nonatomic, strong) id context;
#end
#implementation TDAlertView
#end
And a usage example, note how we pre-cast the pointer:
#implementation SomeAlertViewDelegate
- (void)alertView:(TDAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
NSLog(#"%#", [alertView context]);
}
#end

you can also use the tag property (since it's a UIView subclass). This is just an int, but may be enough for you.

Instead of debating the meaning of "does not support subclassing", I'll provide a better answer. I created a generic contextInfo category for my job a couple months ago. I just put it on github: JLTContextInfo.
#import "objc/runtime.h"
#interface NSObject (JLTContextInfo)
- (NSMutableDictionary *)JLT_contextInfo;
#end
#implementation NSObject (JLTContextInfo)
- (NSMutableDictionary *)JLT_contextInfo
{
static char key;
NSMutableDictionary *contextInfo = objc_getAssociatedObject(self, &key);
if (!contextInfo) {
contextInfo = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &key, contextInfo, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return contextInfo;
}
#end
This creates a place to easily store extra data for any object derived from NSObject. Now the answer is nearly identical to the original question.
-(void) showAlert: (id) ctx {
UIAlertView *baseAlert = [[UIAlertView alloc]
initWithTitle: title
message: msg
delegate:self
cancelButtonTitle: cancelButtonTitle
otherButtonTitles: buttonTitle1, buttonTitle2, nil];
[[baseAlert JLT_contextInfo] setObject:ctx forKey:#"ctx"];
[baseAlert show];
[baseAlert release];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) {
id context = [[alertView JLT_contextInfo] objectForKey:#"ctx"];
[self performSelectorOnMainThread:#selector(xxx:) withObject: context waitUntilDone: NO];
}
}

Subclassing UIAlertView is not a good idea.
http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIAlertView_Class/UIAlertView/UIAlertView.html
Subclassing Notes
The UIAlertView class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.
UIAlertView with a user supplied context and [self autorelease] answers this question in a different way.

I did a mix between Kendall's answer and the uses of blocks in one of my base view controller classes. Now I can use AlertView and ActionSheets with blocks which improves greatly readability. Here is how I did it :
In the .h of my ViewController I declare a block type (optional but recommanded)
typedef void (^AlertViewBlock)(UIAlertView*,NSInteger);
Also I declare a mutable dictionnary that will store the blocks for each alertview :
NSMutableDictionary* m_AlertViewContext;
In the implementation file I add a method to create the AlertView and save the block :
-(void)displayAlertViewWithTitle:(NSString *)title message:(NSString *)message cancelButtonTitle:(NSString *)cancelButtonTitle withBlock:(AlertViewBlock)execBlock otherButtonTitles:(NSArray *)otherButtonTitles
{
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:title
message:message
delegate:self cancelButtonTitle:cancelButtonTitle otherButtonTitles: nil];
for (NSString* otherButtonTitle in otherButtonTitles) {
[alert addButtonWithTitle:otherButtonTitle];
}
AlertViewBlock blockCopy = Block_copy(execBlock);
[m_AlertViewContext setObject:blockCopy forKey:[NSString stringWithFormat:#"%p",alert]];
Block_release(blockCopy);
[alert show];
[alert release];
}
Note that I receive the same attributes as the constructor of the UIAlertView but the delegate (which will be self). Also I receive a AlertViewBlock object that I save in the m_AlertViewContext mutable dictionnary.
Then I show the alert as I would usually do.
In the delegate callbacks, I call the block and give it the parameters :
#pragma mark -
#pragma mark UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString* blockKey = [NSString stringWithFormat:#"%p",alertView];
AlertViewBlock block = [m_AlertViewContext objectForKey:blockKey];
block(alertView,buttonIndex);
[m_AlertViewContext removeObjectForKey:blockKey];
}
- (void)alertViewCancel:(UIAlertView *)alertView {
NSString* blockKey = [NSString stringWithFormat:#"%p",alertView];
[m_AlertViewContext removeObjectForKey:blockKey];
}
Now, whenever I need to use an AlertView I can call it like this :
[self displayAlertViewWithTitle:#"Title"
message:#"msg"
cancelButtonTitle:#"Cancel"
withBlock:^(UIAlertView *alertView, NSInteger buttonIndex) {
if ([[alertView buttonTitleAtIndex:buttonIndex] isEqualToString:#"DO ACTION"]){
[self doWhatYouHaveToDo];
}
} otherButtonTitles:[NSArray arrayWithObject:#"DO ACTION"]];
I did the same for the ActionSheet and now it's really easy to use those.
Hope it helps.

From my other answer, here's a quick and clean solution that takes advantage of associated objects. I mention in my other answer that you could even replace UIAlertView with NSObject and effectively add a context property to any object:
#import <objc/runtime.h>
#interface UIAlertView (Private)
#property (nonatomic, strong) id context;
#end
#implementation UIAlertView (Private)
#dynamic context;
-(void)setContext:(id)context {
objc_setAssociatedObject(self, #selector(context), context, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(id)context {
return objc_getAssociatedObject(self, #selector(context));
}
#end
And then you'll be able to do something like:
NSObject *myObject = [NSObject new];
UIAlertView *alertView = ...
alertView.context = myObject;
IMPORTANT:
And don't forget to nil the context in dealloc!!

You could subclass UIAlertView and add the property there.

Related

Add action to button programmatically

I am trying to add an action to a button programmatically from within a custom class. However, I keep getting an error when I perform the action. I've read a lot about how to do this but am clearly making a mistake somewhere and can't figure out where.
The button is created in the first instance by dragging it onto the storyboard. I then control drag to the ViewControler.h file to get this:
#property (strong, nonatomic) IBOutlet UIButton *testButtonForClass;
In ViewControler.m, I do this:
- (void)viewDidLoad
{
[super viewDidLoad];
testClass *myClass = [[testClass alloc]init];
myClass.myButton = self.testButtonForClass;
[myClass assignActionTargets];
}
Below is the custom class Header and Implementation file.
Header File
#import <Foundation/Foundation.h>
#interface testClass : NSObject
#property (nonatomic, strong) UIButton *myButton;
-(void)assignActionTargets;
#end
Implementation File
#import "testClass.h"
#implementation testClass
-(void)assignActionTargets{
[self.myButton addTarget:
self action:#selector(myButtonInnerTap)
forControlEvents:(UIControlEventTouchUpInside)];
}
-(void)myButtonInnerTap{
UIAlertView *a = [[UIAlertView alloc]initWithTitle:nil
message:#"testClass says hello"
delegate:nil
cancelButtonTitle:#"Dismiss"
otherButtonTitles:nil, nil];
[a show];
}
#end
You create a testClass instance, but you don't keep a reference to, so it gets deallocated at the end of viewDidLoad.
- (void)viewDidLoad {
[super viewDidLoad];
TestClass *myClass = [[TestClass alloc] init];
myClass.myButton = self.testButtonForClass;
[myClass assignActionTargets];
// myClass gets deallocated here!
}
When the button is clicked, it tries to access the target you specify, but now that's a dangling pointer to an invalid memory segment, hence leading to a crash.
You have to keep a strong reference to myClass in order to keep it alive by the time the button is clicked. Declaring a strong property is a good way of achieving that.
#property (nonatomic, strong) TestClass *myThingy;
- (void)viewDidLoad {
[super viewDidLoad];
self.myThingy = [[testClass alloc] init];
self.myThingy.myButton = self.testButtonForClass;
[self.myThingy assignActionTargets];
}
Code style note: Please, use some naming conventions. Class names should be capitalized (I already changed that in the above snippets, since it kills me...) and using myClass for a pointer to an instance of a class is plain disorienting.
First, the double nil for the argument otherButtonTitles is "problematic". Reduce it to one.
Second, make sure the button and the object is properly retained by your calling class.
Finally, make sure via NSLog or breakpoints that the objects in question (custom object, button) are not nil.

replace delegate using block to pass value

I have read so many positive things about using blocks - in particular that it simplifys the code by elimanting delegate calls. I have found examples where blocks are used at end of animation instead of delegate calls. the block example is easy but i cannot use the example to the iphone app.for example i use the delegate:
.h
#protocol AWActionSheetDelegate <NSObject>
- (int)numberOfItemsInActionSheet;
- (AWActionSheetCell*)cellForActionAtIndex:(NSInteger)index;
- (void)DidTapOnItemAtIndex:(NSInteger)index;
#end
#interface AWActionSheet : UIActionSheet
#property (nonatomic, assign)id<AWActionSheetDelegate> IconDelegate;
-(id)initwithIconSheetDelegate:(id<AWActionSheetDelegate>)delegate ItemCount:(int)cout;
#end
.m
- (void)actionForItem:(UITapGestureRecognizer*)recongizer
{
[IconDelegate DidTapOnItemAtIndex:cell.index];
}
and i use it :
-(void)DidTapOnItemAtIndex:(NSInteger)index
{
NSLog(#"tap on %d",index);
}
how use block not use delegate i can get the index,can you give advice and if give good block category to finish the effect is very good . i donot want to use delegate to pass the index,i only want to use block to get the index
I think you are looking for something like this:
//implementation for AWActionSheet's method: actionForItem:withBlock:
-(void) actionForItem:(UITapGestureRecognizer*)recongizer withBlock:(void(^)(NSInteger integer)) block {
NSInteger myInt = 0;
//whatever
//callback
block(myInt);
}
and the call
AWActionSheet* actionSheet;
[actionsheet actionForItem:recognizer withBlock:^(NSInteger integer) {
NSLog(#"myInt: %d", integer);
}];
Use this object:
https://github.com/matteogobbi/MGActionSheet
This is an easy example to init and use the object by block:
//Initialization
MGActionSheet *actionSheet = [[MGActionSheet alloc] initWithTitle:#"Block action sheet!" cancelButtonTitle:#"Cancel" destructiveButtonTitle:#"Delete" otherButtonTitles:#"Option 1", #"Option 2", #"Option 3", nil];
//Show with completition block
[actionSheet showInView:self.view withChoiceCompletition:^(int buttonIndex) {
if(buttonIndex == actionSheet.cancelButtonIndex) NSLog(#"Cancelled");
else if(buttonIndex == actionSheet.destructiveButtonIndex) NSLog(#"Destructed");
else {
NSLog(#"Option at index: %d", buttonIndex);
}
}];

How to show a custom uialertview created in interface builder

I've created a custom uialertview in ib, with two button and a textfield.
Then i create a .h and a .m in this way:
#import <UIKit/UIKit.h>
#interface DialogWelcome : UIAlertView{
IBOutlet UITextField *text;
UIButton *ok;
UIButton *annulla;
}
#property(nonatomic,retain) UITextField *text;
- (IBAction)ok:(id)sender;
- (IBAction)annulla:(id)sender;
#end
and .m :
#import "DialogWelcome.h"
#implementation DialogWelcome
#synthesize text;
-(IBAction)ok:(id)sender{
}
-(IBAction)annulla:(id)sender{
}
#end
i've not implement methods cause in first i need to show it!
i call this in viewcontroller in this way:
- (void)viewDidLoad
{
[super viewDidLoad];
DialogWelcome *alert = [[DialogWelcome alloc] initWithTitle:#"Welcome"
message:nil
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:nil];
[alert show];
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC*2.0);
dispatch_after(delay, dispatch_get_main_queue(), ^{
[alert dismissWithClickedButtonIndex:0 animated:YES];
});
[alert release];
}
This will show a normal uialertview empty with a title. why it doesn't show my custom uialertview?
From the UIAlertView Class Reference:
The UIAlertView class is intended to be used as-is and does not
support subclassing. The view hierarchy for this class is private and
must not be modified.
So, what you're trying to do is explicitly not allowed.

displaying a dialable phone number withing the text for a UIAlertView

is there a way that part of the text that is being displayed in an iphone UIAlertView will be a phone number that when clicked will be dialed?
maybe using tel: somehow ?
If you like to implement the dataDetectorType in your message text there is no native way to do it.
The only way is to subclass the UIAlertView and customize the init method like this :
- (id)initWithTitle:(NSString *)title message:(NSString *)message delegate:(id)delegate cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... {
self = [super initWithTitle:title message:nil delegate:delegate cancelButtonTitle:cancelButtonTitle otherButtonTitles:otherButtonTitles, nil];
if (self) {
CGRect alertFrame = [self frame];
UITextView myTextView = [[UITextView alloc] initWithFrame:CGRectMake(alertFrame.origin.x + 10, alertFrame.origin.y + 44, 200, 44)];
[myTextView setEditable:NO];
[myTextView setBackgroundColor:[UIColor clearColor]];
[myTextView setDataDetectorTypes:UIDataDetectorTypeAll];
[myTextView setText:#"http://www.apple.com"]; // Use your original message string from init
[self addSubview:myTextView];
[myTextView release]
}
return self;
}
I tested it right now and it works but you need to spend a little bit to make it presentable :P
Maybe using the way posted by Jhaliya is quickly and more clean.
Yes, This is possible by setting the delegate ( UIAlertViewDelegate ) of your UIAlertView. Read the message and title from your UIAlertView by using below properties.
#property(nonatomic, copy) NSString *message
#property(nonatomic, copy) NSString *title
You need to implement the delegte method in order to get the info for which button pressed.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
you could also follow link for getting pressed button index and dial an Phone number by Using tel: .
So, your code could be something like below .
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
//Access `title` and `message` of your **UIAlertView**
NSString* alertTitle = alertView.title;
NSString* alertMessage = alertView.message;
// Formatted the phone number and assign it to a string.
NSString* myFormattedPhNumber = /*Use StringWithFormat function of NSString */;
if (buttonIndex == 0)
{
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:myFormattedPhNumber];
}
}

UIAlertView Delegates

Can someone explain how the delegate to a UIAlertView works? Is it automatically called or do I have to call it? Eg:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
Let's say you showed an alert where the delegate was "self"
- (void)showAlert {
UIAlertView *myAlert = [[UIAlertView alloc] initWithTitle:#"My Alert"
message:#"Do you want to continue?"
delegate:self
cancelButtonTitle:nil
otherButtonTitles:#"No", #"Yes", nil];
[myAlert show];
[myAlert release];
}
In order for the following to work in your .m file:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
Your .h file will need to reference the UIAlertViewDelegate in the implementation statement like so:
#interface myViewController : UIViewController <UIAlertViewDelegate> {
}
This is what allows your .m file to respond to UIAlertViewDelegate method calls.
So long as you're correctly setting the delegate property of the UIAlertView and implementing the protocol, it will be automatically called when a user clicks on a button in your alert.
Take a look at the projects listed under "Related sample code" at http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIAlertViewDelegate_Protocol/UIAlertViewDelegate/UIAlertViewDelegate.html to see it in action.
Here is a wrapper for the delegate so that you can use blocks instead. The flow of execution will be the same but the flow of the code will be easier to follow. So, usage:
[YUYesNoListener yesNoWithTitle:#"My Title" message:#"My Message" yesBlock:^
{
NSLog(#"YES PRESSED!");
}
noBlock:^
{
NSLog(#"NO PRESSED!");
}];
...and here is the helper class:
typedef void(^EmptyBlockType)();
#interface YUYesNoListener : NSObject <UIAlertViewDelegate>
#property (nonatomic, retain) EmptyBlockType yesBlock;
#property (nonatomic, retain) EmptyBlockType noBlock;
+ (void) yesNoWithTitle:(NSString*)title message:(NSString*)message yesBlock:(EmptyBlockType)yesBlock noBlock:(EmptyBlockType)noBlock;
#end
#implementation YUYesNoListener
#synthesize yesBlock = _yesBlock;
#synthesize noBlock = _noBlock;
- (id) initWithYesBlock:(EmptyBlockType)yesBlock noBlock:(EmptyBlockType)noBlock
{
self = [super init];
if (self)
{
self.yesBlock = [[yesBlock copy] autorelease];
self.noBlock = [[noBlock copy] autorelease];
}
return self;
}
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0 && self.noBlock)
self.noBlock();
else if (buttonIndex == 1 && self.yesBlock)
self.yesBlock();
[_yesBlock release];
[_noBlock release];
[alertView release];
[self release];
}
- (void) alertViewCancel:(UIAlertView *)alertView
{
if (self.noBlock)
self.noBlock();
[_yesBlock release];
[_noBlock release];
[alertView release];
[self release];
}
+ (void) yesNoWithTitle:(NSString*)title message:(NSString*)message yesBlock:(EmptyBlockType)yesBlock noBlock:(EmptyBlockType)noBlock
{
YUYesNoListener* yesNoListener = [[YUYesNoListener alloc] initWithYesBlock:yesBlock noBlock:noBlock];
[[[UIAlertView alloc] initWithTitle:title message:message delegate:yesNoListener cancelButtonTitle:#"No" otherButtonTitles:#"Yes", nil] show];
}
#end
The alertView:clickedButtonAtIndex: method of the delegate is automatically called by UIAlertView. The init method for UIAlertView takes a delegate as one of the parameters. Just make sure to pass in an object that responds to alertView:clickedButtonAtIndex:.