Constructing a nil-terminated NSString list as an NSString * - objective-c

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];
}

Related

Extract additional NSString from array of JSON parsing data for TableView

I'm parsing my json file and showing it in a grouped table view.
-(void) connectionDidFinishLoading:(NSURLConnection *)connection
{
NSDictionary *allDataDictionary = [NSJSONSerialization JSONObjectWithData:webData options:0 error:nil];
NSDictionary *parks = [allDataDictionary objectForKey:#"Parks"];
NSArray *arrayOfParks = [parks objectForKey:#"Park"];
for (NSDictionary *diction in arrayOfParks) {
NSString *hebName = [diction objectForKey:#"hebName"];
NSString *engName = [diction objectForKey:#"engName"];
NSString *latitude = [diction objectForKey:#"lat"];
NSString *longtitude = [diction objectForKey:#"long"];
[array addObject:hebName];
}
[[self myTableView] reloadData];
}
-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *details = [array objectAtIndex:indexPath.row];
[[[UIAlertView alloc] initWithTitle:#"B7Tour" message:details delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
}
It works very well and I present in my table view the engName.
Clicking on the item at the table view will pop up an alert with the engName itself.
My question is: how to extract the particular latitude and longtitude and save them?
I know how to present them in the table view but I want to extract and save them for additional usage.
Any ideas?
You might want to have the array of dictionaries exist outside this method.
Then you can access them any time.
Then you can maintain another ivar that is your current selection in the table view.
From there it is pretty straight forward to call the objectForKey: on the currently selected object.

Why would a property essentially disappear? Obj-C, Cocoa

I am quite stumped. I have an app with a class for storing item details. Called LEItem. Those items are stored in a store with a class labeled LEItemStore. I have a view with a table of all items. This works fine. If you tap on a row, it sends this message to LogbookFirstViewController.
LogbookFirstViewController *logController = [[LogbookFirstViewController alloc] initForNewItem:NO];
NSArray *items = [[LEItemStore sharedStore] allItems];
LEItem *selectedItem = [items objectAtIndex:[indexPath row]];
NSString *description = [selectedItem description];
NSLog(#"%#", description);
[logController setItem:selectedItem];
[self dismissViewControllerAnimated:YES completion:nil];
That is in a TableView class. In the LogbookFirstViewController.m I have
-(void)setItem:(LEItem *)i{
item = i;
NSString *t = [item description];
NSLog(#"In LogbookFirstViewController, returning %#", t);
}
This is where it gets odd. That works. It outputs the correct item, therefore I would think everything would be okay. But it's not. item is a class-level property, so it should stay, but it doesn't. In the same class, I have overrode this method.
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
//NSString *string = [item description];
//NSLog(#"Item = %#", string);
NSLog(#"View did Appear:animated");
int glucoseValue = [item glucose];
NSString *glucoseString = [NSString stringWithFormat:#"%d", glucoseValue];
[glucoseField setText:glucoseString];
int proteinValue = [item protein];
NSString *proteinString = [NSString stringWithFormat:#"%d", proteinValue];
[proteinField setText:proteinString];
int carbsValuue = [item carbs];
NSString *carbsString = [NSString stringWithFormat:#"%d", carbsValuue];
[carbsField setText:carbsString];
int insulinValue1 = [item insulin];
NSString *insulin1String = [NSString stringWithFormat:#"%d", insulinValue1];
[insulinField1 setText:insulin1String];
int insulinValue2 = [item insulin2];
NSString *insulinString2 = [NSString stringWithFormat:#"%d", insulinValue2];
[insulinField2 setText:insulinString2];
//NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
//[dateFormatter setDateStyle:NSDateFormatterShortStyle];
//[dateFormatter setTimeStyle:NSDateFormatterShortStyle];
//NSLog(#" The item was created on %#", [dateFormatter stringFromDate:[item dateCreated]]);
//[dateButton setTitle:[dateFormatter stringFromDate:[item dateCreated]] forState:UIControlStateNormal];
NSString *t = [item description];
NSLog(#"Loading view... Returns: %#", t);
}
I know that it isn't the cleanest code, but the idea is the same. It uses exactly the same code as the setItem: method. However, this always returns (null). Why? The property appears to go missing at viewWillAppear.
Thanks.
EDIT
I solved the problem. As you can see, the checked answer below did give the right idea, here is what I did to solve it. The problem was that when I sent setItem: I used this code to get LogbookFirstViewController
LogbookFirstViewController *logController = [[LogbookFirstViewController alloc] initForNewItem:NO];
As I know see, that created a new instance of LogbookFirstViewController, so therefore, the existing one did not change it's Item property, as properties are assigned to one instance. Therefore, I was only changing the value of Item for this "invisible" property.
To solve this, one must get the existing instance of the viewController. To do this I did the following:
In LogbookFirstViewController.h I added this property
#property (assign) LogbookFirstViewController *instance;
Then, synthesize instance in your .m and in the same placed I added this to viewDidLoad
- (void)viewDidLoad
{
instance = self;
...
Then, in the other viewController, entriesViewController, I added this too the .h
#property (nonatomic, strong) LogbookFirstViewController *logController;
Synthesize it. Then, I just used my didSelectRowAtIndexPath the same way, just using the existing logController
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *items = [[LEItemStore sharedStore] allItems];
LEItem *selectedItem = [items objectAtIndex:[indexPath row]];
NSString *description = [selectedItem description];
NSLog(#"%#", description);
NSLog(#"Setting controller: %#", logController);
[logController setItem:selectedItem];
[self dismissViewControllerAnimated:YES completion:nil];
}
Then it works!
You have a line where you create the LogbookFirstViewController but you don't actually cause it to display anything (push or present). Since it's a local variable, it would appear that whatever instance of that controller is loading its view is not the same one that you initialize in the code you've shown.
You can verify this by adding a couple of NSLog lines, such as:
NSLog(#"Setting controller: %#", logController); // Insert before existing line
[logController setItem:selectedItem];
...and...
[super viewWillAppear:animated];
NSLog(#"Viewing controller: %#", self); // Insert after existing line
For things to work the way you want, those have to print the same address.
You should retain when assigning object to property without ARC:
-(void)setItem:(LEItem *)i{
_item = [i retain];
...
}
If you use property with ARC, then write _item = i;:
-(void)setItem:(LEItem *)i{
_item = i;
...
}

I want grab 1 random item out of 4 different arrays and display the result

I have an app that uses 3 arrays. The interface is basically 3 picker wheels that one can use to select from a plethora of choices to form a 3 part answer.
But, I need to randomize these not unlike what urban spoon does short of the graphical aspect.
I just need to implement a button that will reach into the arrays and give me a random selection from each array.
Here is a snippet of my code as it sits now...
Any help would be appreciated...
import "DoubleComponentPickerViewController.h"
#implementation DoubleComponentPickerViewController
#synthesize doublePicker;
#synthesize firstTypes;
#synthesize middleTypes;
#synthesize lastTypes;
-(IBAction)buttonPressed
{
NSInteger firstRow = [doublePicker selectedRowInComponent:kfirstComponent];
NSInteger middleRow = [doublePicker selectedRowInComponent:kmiddleComponent];
NSInteger lastRow = [doublePicker selectedRowInComponent:klastComponent];
NSString *first = [firstTypes objectAtIndex:firstRow];
NSString *middle = [middleTypes objectAtIndex:middleRow];
NSString *last = [lastTypes objectAtIndex:lastRow];
NSString *message = [[NSString alloc] initWithFormat:#"%# %# %# test.",first, middle, last];
UIAlertView *alert = [[ UIAlertView alloc] initWithTitle:#"Description:"
message:message
delegate:nil
cancelButtonTitle:#"Cancel"
otherButtonTitles:nil];
[alert show];
[alert release];
[message release];
}
- (void)viewDidLoad
{
NSArray *firstArray = [[NSArray alloc] initWithObjects:
#"a",#"b",#"c",#"d",nil];
self.firstTypes = firstArray;
[firstArray release];
NSArray *middleArray = [[NSArray alloc] initWithObjects:
#"A",#"B",#"C",#"D",nil];
self.middleTypes = middleArray;
[middleArray release];
NSArray *lastArray = [[NSArray alloc] initWithObjects:
#"1",#"2",#"3",#"4",nil];
self.lastTypes = lastArray;
[lastArray release];
You can pick a random element of firstTypes like this:
NSObject *randomFirst = [self.firstTypes objectAtIndex:arc4random_uniform(self.firstTypes.count)];

Objective C UIActionSheet is it possible to fill Title with a NSArray?

I know about addButtonWithTitle function, but I dont want to create new buttons, only to fill the Title area in an UIActionSheet.
If it is possible, is it possible with UIAlertView?
NSMutableString *mstr = [[NSMutableString alloc]init];
for (NSString *s in array)
[mstr appendString:s];
UIActionSheet*actionSheet = [[UIActionSheet alloc] initWithTitle:mstr ......
[actionSheet showInView:view];
[actionSheet release];
[mstr release];
This is assuming yer array contains strings.
Similar approach for UIAlertView.

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

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];
}