Objective-C passing around ... nil terminated argument lists - objective-c

Having some issues with the ... in ObjectiveC.
I'm basically wrapping a method and want to accept a nil terminated list and directly pass that same list to the method I am wrapping.
Here's what I have but it causes an EXC_BAD_ACCESS crash. Inspecting the local vars, it appears when otherButtonTitles is simply a NSString when it is passed in with otherButtonTitles:#"Foo", nil]
+ (void)showWithTitle:(NSString *)title
message:(NSString *)message
delegate:(id)delegate
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ...
{
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:title
message:message
delegate:delegate
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:otherButtonTitles] autorelease];
[alert show];
}
How do I simply siphon from the argument incoming to the argument outgoing, preserving the exact same nil terminated list?

You can't do this, at least not in the way you're wanting to do it. What you want to do (pass on the variable arguments) requires having an initializer on UIAlertView that accepts a va_list. There isn't one. However, you can use the addButtonWithTitle: method:
+ (void)showWithTitle:(NSString *)title
message:(NSString *)message
delegate:(id)delegate
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ...
{
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:title
message:message
delegate:delegate
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:nil] autorelease];
if (otherButtonTitles != nil) {
[alert addButtonWithTitle:otherButtonTitles];
va_list args;
va_start(args, otherButtonTitles);
NSString * title = nil;
while(title = va_arg(args,NSString*)) {
[alert addButtonWithTitle:title];
}
va_end(args);
}
[alert show];
}
This is, of course, very problem-specific. The real answer is "you can't implicitly pass on a variable argument list to a method/function that does not have a va_list parameter". You must therefore find a way around the problem. In the example you gave, you wanted to make an alertView with the titles you passed in. Fortunately for you, the UIAlertView class has a method that you can iteratively call to add buttons, and thereby achieve the same overall effect. If it did not have this method, you'd be out of luck.
The other really messy option would be to make it a variadic macro. A variadic macro looks like this:
#define SHOW_ALERT(title,msg,del,cancel,other,...) { \
UIAlertView *_alert = [[[UIAlertView alloc] initWithTitle:title message:msg delegate:del cancelButtonTitle:cancel otherButtonTitles:other, ##__VA_ARGS__] autorelease]; \
[_alert show]; \
}
However, even with the variadic macro approach, you'd still need a custom macro for each time you wanted to do this. It's not a very solid alternative.

How about constructing an NSInvocation object? Since arguments must be passed by pointer, you could pass the pointer to the nil-terminated list.
You could also iterate over the parameters using marg_list() and construct a nil-terminated list yourself.
These are just simple suggestions; I haven't tried them out.

This is specific to the OP's UIAlertView-wrapping case, and tested only on iOS7: It appears that once a UIAlertView has been initialised with otherButtons:nil, and then has its style set to UIAlertViewStylePlainTextInput it doesn't call its delegate's alertViewShouldEnableFirstOtherButton: to validate input. I'm not sure if this is a bug or intended behaviour but it broke my principle of least astonishment. This is reproducible with the following (I'll assume the delegate's alertViewShouldEnableFirstOtherButton: is implemented):
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Title"
message:#"message"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:nil];
[av setAlertViewStyle:UIAlertViewStylePlainTextInput];
[av addButtonWithTitle:#"OK"];
[av show];
The solution, since UIAlertView happily accepts otherButtons:nil, is to initialise UIAlertView with otherButtonTitles (which can be nil), and iterate over the variadic arguments, as above:
+ (void)showWithTitle:(NSString *)title
message:(NSString *)message
delegate:(id)delegate
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ...
{
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:title
message:message
delegate:delegate
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:otherButtonTitles] autorelease];
// add your [alert setAlertViewStyle:UIAlertViewStylePlainTextInput] etc. as required here
if (otherButtonTitles != nil) {
va_list args;
va_start(args, otherButtonTitles);
NSString * title = nil;
while(title = va_arg(args,NSString*)) {
[alert addButtonWithTitle:title];
}
va_end(args);
}
[alert show];
}

Related

Local variable cause memory leak with ARC

I have a simple question with no answer.
I have a UIAlertView local variable like this,
- (void)insertNewObject:(id)sender
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"title"
message:#"message"
delegate:nil
cancelButtonTitle:#"cancelButtonTitle"
otherButtonTitles:#"otherButtonTitle", nil];
[alert show];
}
When I execute the - (void)insertNewObject:(id)sender method, every time the reference count of the UIAlertView is increased one by one and they are not discarded.
So, any issue is on this with ARC. I need to keep the living objects count without increase while I execute the - (void)insertNewObject:(id)sender method.

Randomizing Alert view text for Objective C

I am new to programming. I am having trouble finding out what all is wrong with this. It is an alert view that i am trying to randomize the text displayed in the message.
-(void)alert:(id)sender{
int randomNumber;
randomNumber = (randomNumber() %3 + 1);
NSLog(#"%i", randomNumber);
if (randomNumber == 1) {
self.YouWin.text = [NSString stringWithFormat:#"You Win"];
}
else if (randomNumber == 2) {
self.YouWin.text = [NSString stringWithFormat:#"You Lose"];
}
else if (randomNumber == 3) {
self.YouWin.text = [NSString stringWithFormat:#"Tie"];
}
NSLog(#"%#",YouWin);
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Hello" message:[NSString stringWithFormat:#"%#",YouWin] delegate:self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
button.hidden = YES;
Try this one:
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Hello"
message:self.YouWin.text
delegate:self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
You needed text-value stored in YouWin, but you passed the YouWin object itself.
*Note: you can use arc4random() for generating random numbers.
There are many good suggestions; I agree with AKB8085.
Replacing the randomNumber() with arc4random() will help at compile time.
But you might want to re-think implementing a random number generator. The reason is in fairness to your user. I pose the question, “Is it fair to assume you want your user to guess a number with this large of a number range?”
Did you Know?
Using the arc4random(3), rand(3), or random(3) you are using a C function.
You are asking the user to guess a number with the following ranges:
arc4random(3) = 0 to 4294967296
rand(3) and random(3) that top out at a RAND_MAX of 0x7fffffff (214748647)
To help in answering your question, answer the following requirement questions:
Is there a min/max range restraint?
What type of compelling delays will happen
by using arc4random(3), rand(3), or random(3)?
Is using NSArray like in the Fisher–Yates_shuffle a better answer?
SUGGESTION:
Read an article on random numbers and NSArray.
NOTE:
Random numbers tend to task the compiler and your user experience will be hindered.
As Anoop noted you are using stringWithFormat but you're not providing a format string at all.
You should do
[NSString stringWithFormat:#"%#", #"You Win"];
but that's extremely redundant, although correct, and it's totally equivalent to just using #"You Win".
Also an advice for the general approach on the problem. Instead of having a big if-else statement, it's better to store all your string into a data structure and then randomly access to it.
In code this would translate to
NSArray * outcomes = #[ #"You Win", #"You lose", #"Tie" ];
int randomIndex = arc4random_uniform(outcomes.count);
NSString * randomOutcome = outcomes[randomIndex];
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Hello"
message:randomOutcome
delegate:self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alert show];
button.hidden = YES;
Note the usage of arc4random_uniform() that gives you back a random number between 0 and the argument provided, excluded.
Replace
randomNumber = (randomNumber() %3 + 1);
with
randomNumber = arc4random() %3 + 1;
Also use this...
if (randomNumber == 1) {
self.YouWin.text = [NSString stringWithFormat:#"You Win"];
}
else if (randomNumber == 2) {
self.YouWin.text = [NSString stringWithFormat:#"You Lose"];
}
else if (randomNumber == 3) {
self.YouWin.text = [NSString stringWithFormat:#"Tie"];
}
NSLog(#"%#",YouWin);
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Hello"
message:self.YouWin.text
delegate:self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alert show];
button.hidden = YES;

UIAlertView showing several times

I've got a simple UIAlertView showing when user runs the app. It has this structure:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Welcome!", "")
message:NSLocalizedString(#"This is a welcome message.", "")
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles: nil];
[alert show];
[alert release];
The question is, How can I customize it to show every 5 runs for example?
Thanks in advance ;)
You can use NSUserDefaults to store app executions count for key AppRunCount (you can introduce your own key name):
int runCount = [[NSUserDefaults standardUserDefaults] integerForKey:#"AppRunCount"] + 1
[[NSUserDefaults standardUserDefaults] setInteger:runCount forKey:#"AppRunCount"];
if (runCount <= 5) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Welcome!", "")
message:NSLocalizedString(#"This is a welcome message.", "")
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles: nil];
[alert show];
[alert release];
}
You can just add code above to viewDidLoad
There will be a method in AppDelegate class like
- (void)applicationDidBecomeActive:(UIApplication *)application
Now create one NSUSerdefaults in this method and make one integer and increment that integer and save it in NSUserdefaults
Now every time application starts that method will be called and integer will incremented
Now make if condition in that method like below
if(your integer which has nsuserdefaults >=5)
{
your alertview
again here make your nsinteger to Zero which is stored in nsuserdefaults
your integer which has nsuserdefaults =0
}
This is the answer of your second question,Every after 5 times app run. alert will be pop up
Let me know it is working or not..!!!!
Happy Coding!!!!

Allocating/showing a UIAlertView in a Block statement

I'm pretty new to blocks in objective C. I've read the docs and I have a pretty basic understanding of them.
Why won't this work? This is a framework callback for requesting Calendar access. It takes a block as an argument. All I want to do is allocate and show the UIAlertView in the block, but it will crash when it tries to show.
I hope this isn't a silly question... all the intro examples on the net using blocks just show trivial examples with counters.
//Request access
[eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
if (granted == FALSE) {
UIAlertView *myAlert = [[[UIAlertView alloc]initWithTitle:#"Calendar Access Denied"
message:#"<InfoText>"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil] autorelease];
[myAlert show];
}
else {
[self addToCalendar];
}
}];
have you tried?
if (granted == FALSE)
{
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *myAlert = [[[UIAlertView alloc]initWithTitle:#"Calendar Access Denied"
message:# <InfoText>"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil] autorelease];
[myAlert show];
});
}
this makes calls back in the main thread, useful for mixing blocks and UIKit

Constructing a nil-terminated NSString list as an NSString *

There are many methods in the SDK that ask for a list of strings, terminated by a nil, for example, in UIActionSheet:
- (id)initWithTitle:(NSString *)title delegate:(id < UIActionSheetDelegate >)delegate cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ...
'otherButtonTitles' in this case is a list of NSStrings terminated with a nil. What I'd like to do is call this method with a constructed NSMutableArray of NSStrings, because I'd like to create and order the arguments dynamically. How would I do this? I'm not sure how to create a nil-terminated pointer to NSStrings in this case, and if passing it in would even work. Do I have to alloc the memory for it manually and release it?
You cannot convert any array into a variadic list.
However, for UIActionSheet, you could add those otherButtonTitles after the sheet is created, using -addButtonWithTitle:
UIActionSheet* sheet = [[UIActionSheet alloc] initWithTitle:...
/*etc*/
otherButtonTitles:nil];
for (NSString* otherButtonTitle in otherButtonTitlesArray)
{
[sheet addButtonWithTitle:otherButtonTitle];
}
I need to make a dynamic action sheet as well. So I made a mostly empty action sheet. Added my buttons. Then added the Cancel button and marked it as cancel.
sheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:homeName, nil];
[sheet addButtonWithTitle:otherButton1];
[sheet addButtonWithTitle:otherButton2];
[sheet addButtonWithTitle:#"Cancel"];
[sheet setCancelButtonIndex:[sheet numberOfButtons] - 1];
sheet.actionSheetStyle = UIActionSheetStyleBlackTranslucent;
[sheet showInView:self.view];
[sheet release];
You sure CAN convert NSArray to va_list. For example to use with NSString
- (id)initWithFormat:(NSString *)format arguments:(va_list)argList
Like this:
+ (id)stringWithFormat:(NSString *)format array:(NSArray *)arguments;
{
NSRange range = NSMakeRange(0, [arguments count]);
NSMutableData *data = [NSMutableData dataWithLength:sizeof(id) * [arguments count]];
[arguments getObjects:(__unsafe_unretained id *) data.mutableBytes range:range];
return [[NSString alloc] initWithFormat:format arguments:data.mutableBytes];
}