Can a UIAlertView pass a string and an int through a delegate - iphone

I have a UIAlertView (several, in fact), and I'm using the method -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex to trigger an action if the user doesn't press cancel. Here's my code:
- (void)doStuff {
// complicated time consuming code here to produce:
NSString *mySecretString = [self complicatedRoutine];
int myInt = [self otherComplicatedRoutine];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"HERE'S THE STUFF"
message:myPublicString // derived from mySecretString
delegate:nil
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Go On", nil];
[alert setTag:3];
[alert show];
[alert release];
}
and then what I would like to do would be the following:
- (void)alertView:(UIAlertView *)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 1) {
if ([alertView tag] == 3) {
NSLog(#"%d: %#",myInt,mySecretString);
}
}
}
However, this method doesn't know about mySecretString or myInt. I definitely don't want to recalculate them, and I don't want to store them as properties since -(void)doStuff is rarely, if ever, called. Is there a way to add this extra information to the UIAlertView to avoid recalculating or storing mySecretString and myInt?
Thanks!

Probably the quickest way to associate an object with an arbitrary other object is to use objc_setAssociatedObject. To use it correctly, you need an arbitrary void * to use as a key; the usual way to do that is to declare a static char fooKey globally in your .m file and use &fooKey as the key.
objc_setAssociatedObject(alertView, &secretStringKey, mySecretString, OBJC_ASSOCIATION_RETAIN);
objc_setAssociatedObject(alertView, &intKey, [NSNumber numberWithInt:myInt], OBJC_ASSOCIATION_RETAIN);
Then use objc_getAssociatedObject to retrieve the objects later.
NSString *mySecretString = objc_getAssociatedObject(alertView, &secretStringKey);
int myInt = [objc_getAssociatedObject(alertView, &intKey) intValue];
Using OBJC_ASSOCIATION_RETAIN, the values will be retained while attached to the alertView and then automatically released when alertView is deallocated.

Related

iPhone / iPod Button in background shearing through to UIAlertView TextBox

As I type into a textbox on a UIAlertView, an ImageButton in the background starts shearing through. It only happens when the text gets close to the image.
The code for the alertview is as follows:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"... number"
message:nil
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Ok", nil];
[alert setAlertViewStyle:UIAlertViewStylePlainTextInput];
[[alert textFieldAtIndex:0] setKeyboardType:UIKeyboardTypeNumberPad];
[[alert textFieldAtIndex:0] becomeFirstResponder];
[alert setTag:1];
[alert setOpaque:true];
[alert show];
//EDIT
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (alertView.tag == 2)
{
NSString* title = [alertView buttonTitleAtIndex:buttonIndex];
if ([title isEqualToString:#"Cancel"])
{
[self doSomething1];
}
if([alertView.title isEqualToString:#"Are you sure?"] && [title isEqualToString:#"YES"])
{
[self doSomething2];
}
}
}
- (BOOL)alertViewShouldEnableFirstOtherButton:(UIAlertView *)alertView
{
if (alertView.tag == 1){
NSString *inputText = [[alertView textFieldAtIndex:0] text];
if( [inputText length] == 10 )
{
NSUserDefaults* def = [NSUserDefaults standardUserDefaults];
[def setObject:inputText
forKey:#"FIELD"];
[def synchronize];
return YES;
}
else
{
return NO;
}
}
return YES;
}
I have tried setting it to opaque, I have tried looking for z-indexing values, and I have also tried scaling the image down to the smallest it needs to be. Taking screenshots does not help. This only happens in iOS 7.
UIAlertView are not shown in the same UIWindow as the rest of your view controllers. They are shown in their own UIWindow with a windowLevel of UIWindowLevelAlert. This other window is transparent (mostly) and renders on top of your main window. This makes UIAlertView objects not interact as well with other views you may have on screen. Do not modify any properties from the underlying UIView class.
The code you show above has if (alertView.tag == 1) as well as if (alertView.tag == 2), but it only ever creates a UIAlertView with a tag value of 1. Is there somewhere else you are creating a second alert? Are you possibly showing a second alert before the first one completes its disappearance animation? That has been known to cause graphical tears.
The delegate method -alertViewShouldEnableFirstOtherButton: can be called from within the -drawRect: method of the UIAlertView. The draw method must be extremely fast to avoid causing graphical tearing. Yours calls NSUserDefaults -synchronize, which performs a disk write operation. You should not perform any operation within your -alertViewShouldEnableFirstOtherButton: method, besides determining the response value. If you want to be notified of changes to the text field, assign yourself as the UITextFieldDelegate and implement -textField:shouldChangeCharactersInRange:replacementString:. That method should be safe to call NSUserDefaults -synchronize from.
As an additional tip, you could make your code much easier to manage using the PSPDFAlertView.

How do you pass a variable to the UIAlertView delegate?

How do you pass a variable to the UIAlertView delegate?
I have a variable that I want to use in the alert view delegate. It is only used in the function that shows the UIAlertView and the UIAlertView delegate, so i don't think it should be a property on the controller. Is there a way to attach the variable to UIAlertView and retrieve it in the delegate?
- (void) someUserCondition:(SOCode *)userCode {
if ([userCode warrentsConfirmation] > 0) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Title" message:#"Are you sure?" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"OK",nil];
[alert setAlertViewStyle:UIAlertViewStyleDefault];
//TODO somehow store the code variable on the alert view
[alert show];
}
}
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
NSString *title = [alertView buttonTitleAtIndex:buttonIndex];
if ([title isEqualToString:#"OK"]){
SOCode *userCode = //TODO somehow get the code from the alert view
[self continueWithCode:code];
}
}
in .h before interface:
extern const char MyConstantKey;
#interface ViewController...
in .m import:
import <objc/runtime.h>
in .m before implementation
const char MyConstantKey;
in .m implementation
-(void)viewDidAppear:(BOOL)animated{ //or wherever
NSString *aString = #"This is a string";
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Testing" message:#"test is test" delegate:self cancelButtonTitle:#"Okay" otherButtonTitles:nil];
[alert show];
[alert release];
objc_setAssociatedObject(alert, &MyConstantKey, aString, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
in .m alertview callback
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
NSString *associatedString = objc_getAssociatedObject(alertView, &MyConstantKey);
NSLog(#"associated string: %#", associatedString);
}
Use Associated Objects. It is described in more detail here: Your New Friends: Obj-C Associated Objects
To set the object you use use:
objc_setAssociatedObject(alert, &key, userCode, OBJC_ASSOCIATION_RETAIN);
And then to get it back:
SOCode *userCode = objc_getAssociatedObject(alertView, &key);
You also need to add static char key; so that it is in the scope of moth methods.
Update
I have wrapped this into a category on UIAlertView. You can use Cocoapods to bring it in:
pod 'HCViews/UIAlertViewHCContext', '~> 1.2'
The source is available here: https://github.com/hypercrypt/HCViews/blob/master/Categories/UIAlertView%2BHCContext.h
A lot of posts talk about the concepts behind associated objects (which is good!) but sometimes you just want to see the code. Here's a clean and quick category that you can either put in a separate file or above the interface of one of your existing .m files (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!!
UIAlertView is a subclass of UIView which has a tag property you can set to an integer. Unfortunately if you need something other than an integer to identify/pass info to the delegate than you will need to set some properties (or set up an array with the tag indexing into it) on the delegate itself. Advaith's way will probably work but is technically not supported by Apple.
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Title" message:#"Are you sure?" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"OK",nil];
[alert setAlertViewStyle:UIAlertViewStyleDefault];
alert.tag = SOMEINTEGER;
[alert show];
I suspect the most straight-forward way is a property in the alert view's delegate class. An alert view doesn't have any provision for "user info" and doesn't support sub-classing, which removes the only shortcuts that come to mind.
Subclass UIAlertView, add a property called userInfo with type of your choice. Set the user info value at the time you create an instance of Subclassed UIAlertView, and retrieve it from within the delegate method. (There you will get the subclassed instance which holds the userInfo)

UIAlertViewStylePlainTextInput - Program continues without waiting for text input from user

I'm sort of stuck on a UIAlertViewStylePlainTextInput. (My code is too long to post here, but this is the part where the problem exists.) It all sort of works, but the program doesn't wait until the user enters the information before continuing. The "editName" button doesn't change the display in the code below, but the updateDisplay button works fine (if you've pressed the "editName" button before you pressed "updateDisplay"). How can I get everything to sort of halt until the user enters the info (or cancels). Or is there some other lead that will take me where I need to go. (I've been toying with putting the rest of the code in the -(void) alertView, but that just doesn't seem to be the correct way of doing it).
Thanks in advance.
#import "HSTestViewController.h"
#implementation HSTestViewController
#synthesize display;
NSString *newName;
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 1)
{
newName = [[alertView textFieldAtIndex:0] text];
}
}
- (void) getNewName;
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"You made the High Score List"
message:#"Please enter your name"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Ok", nil];
alert.alertViewStyle = UIAlertViewStylePlainTextInput;
[alert show];
}
- (IBAction)editName:(id)sender
{
[self getNewName];
newName = display.text;
}
- (IBAction)updateDisplay:(id)sender
{
display.text = newName;
}
#end
Add UIAlertViewDelegate to the class. And implement the below delegate method.
OK button index value will be 1 in your case.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex)
{
// call the method or the stuff which you need to perform on click of "OK" button.
}
}
Hope this helps.

Multiple UIActionSheets on the same delegate

I'm writing a puzzle game. When the user presses the check button, I see if the solution they entered is correct. Depending on the result, I present one of two action sheets for them. For now I just have some NSLog statements to make sure things are getting called, but only one of the sheets seems to work properly.
Nothing gets called when I click a button in showErrorsActionSheet. The action sheet disappears off the screen, but the logs never print.
I suspect it has something to do with having two actionsheets declared to the same delegate (self)
- (void) checkSolution {
//code determines the value of the BOOL allCorrect
if (allCorrect) { //IF ALL OF THE LETTERS WERE CORRECT
//display UIAlertView;
NSLog(#"allCorrect");
UIActionSheet *levelCompleteActionSheet = [[UIActionSheet alloc] initWithTitle:#"Congratulations! You Have Finished the Level!" delegate:self cancelButtonTitle:#"Review my work" destructiveButtonTitle:#"Choose next puzzle" otherButtonTitles:nil, nil];
[levelCompleteActionSheet showInView:self.view];
[levelCompleteActionSheet release];
}
else {
//[self showIncorrectLettersInRed];
UIActionSheet *showErrorsActionSheet = [[UIActionSheet alloc] initWithTitle:#"Sorry, thats not right. Show errors in red?" delegate:self cancelButtonTitle:#"No Thanks, I'll keep trying" destructiveButtonTitle:#"Yes please, I'm stuck!" otherButtonTitles:nil, nil];
[showErrorsActionSheet showInView:self.view];
[showErrorsActionSheet release];
}
}
the methods that are supposed to be called are:
- (void) levelCompleteActionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex != [actionSheet cancelButtonIndex]) {
NSLog(#"return to levelSelect");
//pushViewController:levelSelect
}
else {
NSLog(#"continue to examine solution");
}
}
- (void) showErrorsActionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex != [actionSheet cancelButtonIndex]) {
NSLog(#"show errors in red");
}
else {
NSLog(#"continue to try");
}
}
and Ive declared the UIActionSheet protocol in the interface file as follows:
#interface GamePlay : UIViewController <UIActionSheetDelegate> {
Set a tag for each actionSheet, then use a switch statement in the UIActionSheet delegate.
Assign a tag
- (void)checkSolution
{
if (allCorrect)
{
UIActionSheet *levelCompleteActionSheet = [[UIActionSheet alloc] initWithTitle:#"Congratulations! You Have Finished the Level!" delegate:self cancelButtonTitle:#"Review my work" destructiveButtonTitle:#"Choose next puzzle" otherButtonTitles:nil, nil];
[levelCompleteActionSheet setTag: 0];
[levelCompleteActionSheet showInView:self.view];
[levelCompleteActionSheet release];
}
else
{
UIActionSheet *showErrorsActionSheet = [[UIActionSheet alloc] initWithTitle:#"Sorry, thats not right. Show errors in red?" delegate:self cancelButtonTitle:#"No Thanks, I'll keep trying" destructiveButtonTitle:#"Yes please, I'm stuck!" otherButtonTitles:nil, nil];
[showErrorsActionSheet setTag: 1];
[showErrorsActionSheet showInView:self.view];
[showErrorsActionSheet release];
}
}
UIActionSheet Delegate
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
switch ( actionSheet.tag )
{
case 0: /* levelCompleteActionSheet */
{
switch ( buttonIndex )
{
case 0: /* 1st button*/
break;
case 1: /* 2nd button */
break;
}
}
break;
case 1: /* showErrorsActionSheet */
break;
}
}
The same would apply anywhere else in this class as well, including levelCompleteActionSheet: and showErrorsActionSheet:. The only difference is, you would need to create an iVar for each actionSheet instead of creating them in checkSolution.
The methods that will be called by a UIActionSheet on its delegate are the methods listed in the UIActionSheetDelegate protocol.
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIModalViewDelegate_Protocol/UIActionSheetDelegate/UIActionSheetDelegate.html
To be called, your method must be one of those methods. I don't see levelCompleteActionSheet or showErrorsActionSheet listed in that protocol! :) Your method must be named actionSheet:clickedButtonAtIndex:, and not some name you make up out of whole cloth.
Using Tag solve this problem
levelCompleteActionSheet.tag = 100;
showErrorsActionSheet.tag = 101;
- (void) levelCompleteActionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
if(actionSheet.tag == 100){
// levelCompleteActionSheet implement your required function
}
else if(actionSheet.tag == 101){
// showErrorsActionSheet implement your required function
}
}

Can i pass the parameter to clickedButtonAtIndex that is iphone method?

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
....
}
I'm developing iphone app by Xcode.
When i click the button of UIAlertView, I have to modify some local variables in that method.
I want to control some local variables in that method.
So is there the way to pass the variables as parameter ?
No you cannot pass anything else to that method, but if you are going to modify local variables then what is the point?, if you want to use some information on that method you will have to set that information in an instance variable.
So for example before showing your AlertView something like this:
self.myInstanceVariable = valueIWillNeedWhenClicked;
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Title" message:#"Message" delegate:self cancelButtonTitle:#"NO" otherButtonTitles:#"YES", nil];
alertView.delegate = self;
[alertView show];
[alertView release];
Then on method:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *valueINeed = self.myInstanceVariable
}