Accessing UI Input elements as I would in HTML - objective-c

I am dumping multiple inputs into a view. They consist of UITextFields UIPickerViews and UIDatePickers.
Each of them have an ID and a Key that need to be saved when the input value is saved. So when the 'Save' button is clicked, I need to loop through and store something like:
{
ID: 'inputid',
Key: 'yearly',
Value: (UITextField value)
}
In HTML, I would just add these values to the input (<input type="text" id="inputid" name="yearly" />) and then loop through each one using $(input).attr('id') etc.
In Objective-C, the only way I can think to do this is to keep a hashtable of this information when I draw the inputs, and then store some kind of identifier against the 'tag' field of the UITextField, then read that by getting all of the inputs from a view and comparing them to the hashtable.
Is this the right way to go about it?? Am I missing something simple here? How would you go about it?
EDIT
To better frame the situation, the number of UITextFields on the page is being pulled from an XML file, therefore I don't know how many UITextFields there will be (so can't assign them to the controller necessarily)
I need something along the lines of:
foreach(var question in ArrayOfQuestions) {
UITextField *textField = [[UITextField alloc] initWithFrame:];
textField.attributes["id"] = question.Id;
textField.attributes["key"] = question.Key;
}
and in the save method
foreach(var textField in UIView) {
textField = (UITextField)textField;
NSString *id = textField.attributes["id"];
NSString *key = textField.attributes["key"];
}
This maybe something I could find in google but can't think of the right search terms and keep coming up empty handed. On the same level, if you can better describe my request please update the title of my question

I think you are actually at the best solution, in regards to the hash table (NSDictionary) of attribute data. It is really a bad design decision to have too much semantic data in the view object itself, as it has nothing to do with the view.
What you need to do concretely in code is the following:
To set up your views & attribute data:
UIView *containerView; // The view that contains your UITextViews.
NSMutableDictionary *attributes; // A dictionary mapping tags to questions.
NSMutableArray *arrayOfQuestions; // The questions that you've parsed from a file or whatever.
// ...
// Each "question" would be of the form #{ #"id" : ____, #"key" : ____ }
for (NSDictionary *question in arrayOfQuestions) {
UITextField *textField = [[[UITextField alloc] initWithFrame:aFrame] autorelease];
[containerView addSubview:textField];
textField.tag = getATag(); // However you want to tag them.
// Fancy new objective-C container/object-literal syntax :)
attributes[#(textField.tag)] = question;
}
Then for your "save" method:
for (UIView *childView in containerView.subviews) {
if ([childView isKindOfClass:[UITextView class]]) {
// We know the class and can thus safely typecast the UIView.
UITextField *textField = (UITextField *)childView;
NSDictionary *aQuestion = attributes[#(textView.tag)];
// Now you can access the id and key properties of the question.
// ... Whatever else you want to do.
}
}
The enumerated loop over the subviews is I think the big thing you were looking for here. It is very similar to the way that you would do it in jQuery with selectors.

If you make each of the elements a property of the view controller, you can access them directly from anywhere and get the current value.
So in the method attached to the save button, you can get the current string value of a UITextField like this, for example:
NSString *currentTextFieldString = self.someTextField.text;

Related

Writing an app (for my class) which states user input on state names and that particular state's capital.

The app is supposed to work like this:
The user inputs, in a text field, a particular state. In another text field, the user inputs a capital for that state.
After the user hits the "add" button, the key/value pair is sent into both dictionaries, one for pairing State to Capital, and another for comparing Capital to State.
So here's my problem:
All that being said, and the app programmatically creating a button in a scrollView, the State or whichever data was typed into the first field, is represented.
That particular button is supposed to, upon clicking, switch the name of that button to the paired Capital or State, in whichever case. I cannot, for the life of me, find out how to code this into my app.
My Header File:
IBOutlet UIScrollView *scrollView; // for scrollable State / Capital view
IBOutlet UITextField *stateField; // text field for entering state
IBOutlet UITextField *capitalField; // text field for entering capital
// stores the website and passwds
NSMutableDictionary *stateCapital;
NSMutableDictionary *capitalState;
// stores the Buttons representing the passwds
NSMutableArray *buttons;
// stores the info buttons for editing existing passwd
NSMutableArray *label;
// location of the file in which capital are stored
NSString *filePath;
My Implementation:
To add a state
// make the keyboard disappear
[stateField resignFirstResponder];
[capitalField resignFirstResponder];
NSString *key = stateField.text; // get the text in tagField
NSString *value = capitalField.text; // get the text in queryField
// test if either field is empty
if (value.length == 0 || key.length == 0)
return; // exit from the method
if ([stateCapital valueForKey:key] == nil) // test if the website already exists
[self addNewButtonWithTitle:key]; // if not, add a new button
[stateCapital setValue:value forKey:key]; // add a new entry in tags
stateField.text = nil; // clear tagField of text
capitalField.text = nil; // clear queryField of text
[stateCapital writeToFile:filePath atomically:NO]; //save the data
To add a Capital (just reverse the process)
// make the keyboard disappear
[stateField resignFirstResponder];
[capitalField resignFirstResponder];
NSString *key = capitalField.text; // get the text in tagField
NSString *value = stateField.text; // get the text in queryField
// test if either field is empty
if (value.length == 0 || key.length == 0)
return; // exit from the method
if ([capitalState valueForKey:key] == nil) // test if the website already exists
[self addNewButtonWithTitle:key]; // if not, add a new button
[capitalState setValue:value forKey:key]; // add a new entry in tags
stateField.text = nil; // clear tagField of text
capitalField.text = nil; // clear queryField of text
[capitalState writeToFile:filePath atomically:NO]; //save the data
I'm pretty new to programming, and am taking a Summer course, it's extremely compact.
My professor is in and out of the lab, but offers only hints on how to achieve the success of this application. I'm trying to find out via the web and my book on how to do this, but have no success as of yet.
Generally, you want individual IBOutlets for each UI widget that's part of your interaction. (I assume you're using Storyboard…) You have outlets for UITextFields, but your buttons are in an NSMutableArray. This is almost surely not what you want. You also don't have any methods written that handle the extraction of the data from the text fields into the dictionaries you're keeping. So, you need to wire a button to an IBAction method. This method will extract the text from the UITextField IBOutlets (self.stateField.text) and save to the NSMutableDictionary. Be sure your IBOutlets are connected properly to your view controller code and that the buttons are properly wired to your IBAction method. Those keywords should get you on the right track to solving the question.
Related: I see a lot of info about "websites" and "passwds" in your code. This leads me to think you've copy-pasted some other code and are trying to shoehorn your assignment into this code or else this used to be a different assignment and your instructor gave you a starting point that was poorly modified.
Bonus free editorial — It's not 100% clear if the instruction to keep two dictionaries with the same data in reverse order is inherent to the assignment, it's overkill IMHO since you have the allKeysForObject: method to get the key that corresponds to the object being searched. So, NSDictionary *capitalsDictionary = #{#"Texas": #"Austin"} can give you Austin for the key Texas with capitalsDictionary[#"Texas"] and can give you Texas for the value Austin with [capitalsDictionary allKeysForObject:#"Austin"][0] (The [0] is because allKeys… returns an array. If you check this before inserting a new capital, you can be sure that there will only be one. More than one would be a mistake unless somehow more than one state's capital city would be the same name, which is not the case in the USA, but might be elsewhere. By using allKeysForObject: you'd be able to handle this situation but with the multiple dictionaries, you wouldn't because a key can correspond to only one value. You could make that value an array, but it would still have the same problem as allKeys…

NSPopupButton Bindings with Value Transformer

I don't know if what I see with a popup button populated by bindings with a value transformer is the way it's supposed to be or not -- the unusual thing I'm seeing (at least with respect to what I've seen with value transformers and table views) is that the "value" parameter in the transformedValue: method is the whole array bound to the array controller, not the individual strings in the array. When I've done this with table views, the transformer is called once for each displayed row in the table, and the "value" parameter is whatever object is bound to that row and column, not the whole array that serves as the content array for the array controller.
I have a very simple app to test this. In the app delegate there is this:
+(void)initialize {
RDTransformer *transformer = [[RDTransformer alloc] init];
[NSValueTransformer setValueTransformer:transformer forName:#"testTransformer"];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.theData = #[#{#"name":#"William", #"age":#"24"},#{#"name":#"Thomas", #"age":#"23"},#{#"name":#"Alexander", #"age":#"64"},#{#"name":#"James", #"age":#"47"}];
}
In the RDTransformer class is this:
+ (Class)transformedValueClass {
return [NSString class];
}
+(BOOL)allowsReverseTransformation {
return NO;
}
-(id)transformedValue:(id)value {
NSLog(#"%#",value);
return value;
}
In IB, I added an NSPopupButton to the window and an array controller to the objects list. The content array of the controller is bound to App Delegate.theData, and the Content Values of the popup button is bound to Array Controller.arrangedObjects.name with the value transformer, testTransformer.
When I run the program, the log from the transformedValue: method is this:
2012-09-19 20:31:39.975 PopupBindingWithTransformer[793:303] (
)
2012-09-19 20:31:40.019 PopupBindingWithTransformer[793:303] (
William,
Thomas,
Alexander,
James
)
This doesn't seem to be other people's experience from what I can see on SO. Is there something I'm doing wrong with either the bindings or the value transformer?
Unfortunately, this is how NSPopUpButton works. The problem is not limited to that control. If you try binding an NSArrayController.contentArray to another NSArrayControllers.arrangedObject.someProperty you will get the same problem. Here is a simple workaround that I use in all my value transformers, which makes them work with both tables and popups:
You can modify your value transformer in the following way:
-(id)transformedArrayValue:(NSArray*)array
{
NSMutableArray *result = [NSMutableArray array];
for (id value in array)
[result addObject:[self transformedValue:value]];
return result;
}
-(id)transformedValue:(id)value
{
if ([value isKindOfClass:[NSArray class]])
return [self transformedArrayValue:value];
// Do your normal-case transform...
return [value lowercaseString];
}
It's not perfect but it's easy to replicate. I actually put the transformedArrayValue: in a class category so that I don't need to copy it everywhere.

arraycontroller nsmutablearray add programmatically

I trying to add a data on table view via Array Controller thats bind to a NSMutableArray.
On the IB property it looks like this :
and on the code I tried to add the NSMutableArray dynamically then reload the view, bu nothings happened.
for(int i=0;i<10;i++){
NSMutableDictionary *group = [[NSMutableDictionary alloc]init];
[group setValue:[NSString stringWithFormat:#"%#-%d", #"Group", i] forKey:#"groupname"];
[contentArray addObject:group];
}
[tableContent reloadData];
I have been google it and browse the same question in stackoverflow, not found a useful one.
any idea ?
Thanks
updated
I wrote above code in File's owner class.
I think the problem is that the array needs to send a KVO notification to the array controller (or maybe it's the table view, I'm not sure). The way to do that is:
self.contentArray = contentArray; (or _contentArray if that's what your ivar is called). I'm assuming that contentArray is a property, if not, you should make it one.

Populate and bind an NSTableView to multiple array controllers

I have an API provided NSArray with a bunch of content objects – we'll call this acquiredFruit – and an empty NSMutableArray called likedFruit.
I've created NSArrayControllers for both arrays and bound my TableView to acquiredFruit.arrangedObjects. The first column of the tableView is bound to arrangedObjects.name and correctly shows all the delicious fruit.
I've created a second column with a checkbox – when the user fills the box I'd like to add the fruit to my likedFruit array. Unchecking the box should remove the fruit object from the likedFruit array.
Essentially I'd like my NSTableView to join between two array controllers. I have a feeling I should be making a single separate controller for this, but I'm unsure how to approach the problem.
I should also mention that I'm aware I could iterate through my array and construct another object with the fields I need, but my goal is to do this by using bindings, if possible.
Thoughts?
I think you should use one array controller.
You can have an attribute on Fruit called liked. Now your "liked" checkbox column is connected to arrangedObjects.liked. Later, when you want to determine the set of all liked fruits, you can query your fruits array:
NSArray * likedFruits = [ allFruitsArray filteredArrayUsingPredicate:[ NSPredicate predicateWithFormat:#"liked = YES"] ] ;
If in another part of your UI you are displaying only liked fruit, you can set your array controller's filterPredicate to the predicate above to get just those fruits.
EDIT: Let's say NSFruit is provided via someone else's API. Let's use the "General Technique for Adding Properties to Someone Else's Class":
#interface NSFruit (Liking)
#property ( nonatomic ) BOOL liked ;
#end
#implementation NSFruit (Liking)
-(BOOL)liked
{
return [ objc_getAssociatedObject( self, "_abliked" ) boolValue ] ;
}
-(void)setLiked:(BOOL)b
{
objc_setAssociatedObject( self, "_abliked", [ NSNumber numberWithBool:b ], OBJC_ASSOCIATION_RETAIN_NONATOMIC ) ;
}
#end
(I've written this same code for like 100 posts recently!)
I'm not at my Xcode computer right now, so i can't test this, but it seems like you don't really need another array controller, but just another array to hold the likedFruits. I think you need to create an array of dictionaries from your acquiredFruits array that would have one key for the fruit name and another key with a bool value for whether the check box is checked --this bool would be bound to your second column. I'm not sure about the next step on how to tell the likedFruit array that it need to add a new fruit -- I think the check box could have an action method that you could use to have the likedFruit array add the object in the row where the check box was clicked.
After Edit:
Here is an example of how to do what I suggested. I take an array of fruits and turn it into an array of dictionaries (called theData) that include a key for the value of your check box (In IB the content array of the array controller is bound to theData, and the columns are bound to Array Controller.arrangedObjects.fruitName and Array Controller.arrangedObjects.isLiked). checkChanged is an IBAction connected to the check box (but note the sender is actually the table view), and I use the value of the check box to determine whether to add a fruit to likedFruits or delete one. I put one more method, connected to a button just to check the values in likedFruits.
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.theData = [NSMutableArray array];
self.likedFruit =[NSMutableArray array];
NSArray *acquiredFruits = #[#"Apple",#"Orange",#"Pear",#"Peach"];
for (NSString *aFruit in acquiredFruits) {
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:aFruit,#"fruitName",[NSNumber numberWithBool:NO],#"isLiked", nil];
[self.theData addObject:[dict mutableCopy]];
}
self.theData = _theData;
// NSLog(#"%#",self.theData);
}
-(IBAction)checkChanged:(NSTableView *)sender { //connected to the button cell in the table view (but sender is the table view)
NSString *theFruit = [[self.controller.arrangedObjects objectAtIndex:sender.clickedRow ] valueForKey:#"fruitName"];
BOOL doWeLikeIt = [[[self.controller.arrangedObjects objectAtIndex:sender.clickedRow] valueForKey:#"isLiked"] boolValue];
if (doWeLikeIt) {
[self.likedFruit addObject:theFruit];
}else{
[self.likedFruit removeObject:theFruit];
}
}
-(IBAction)logLikedFruits:(id)sender {
NSLog(#"%#",self.likedFruit);
}

How to pass in a a parameter on a button click in Objective C?

So I am trying pass in parameters to my "buttonClicked" function so that I can dynamically control what happens when you click the button. Every way that I try to do it on my own just breaks my code. I have searched many different sites and answers on stackOverflow, but I can't seem to find an answer. I am fairly new to objective C, especially with functions, So I could really use some help figuring out how to do this. Thanks in advance
Here is my code thus far and what I am trying to do:
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
NSLog(#"Hi 1!");
[button addTarget:self action:#selector(buttonClicked:buttonType:buttonID:) forControlEvents:UIControlEventTouchUpInside];
button.frame = CGRectMake(buttonViewXvalue, buttonViewYvalue, buttonViewWidth, buttonViewLength);
[self.view addSubview:button];
Then the Declaration in the Header File:
- (IBAction)buttonClicked:(id)sender theButtonType: (int)buttonType: theButtonID: (int) buttonID;
and the implementation:
- (IBAction)buttonClicked:(id)sender theButtonType: (int)buttonType: theButtonID: (int) buttonID
{
//Here I would use the buttonType and buttonID to create a new view.
NSLog(#"Hi!");
}
You can't use multi-parameter methods with addTarget:action:forControlEvents:. Instead you might set the button's tag, then look up information later based on the tag.
The action you add to UIButton (or any UIControl for that matter) must have a signature like (void)actionName or (void)actionName:(id)sender; as defined by target-action design pattern.
That gives you two simple solutions. One is that each of your buttons calls different (void)actionName-like method, which then calls a more complex method on self and passes the required parameters. Other way is to give each of your buttons a tag property and have them call (void)actionName:(id)sender-like method (they can all call the same one) and then you call the right method with right parameters depending on this tag:
- (void)actionName:(UIButton)sender;
{
if (sender.tag == 1) {
[self firstMethodWithString:someString andNumber:someNumber];
} else if (sender.tag == 2) {
[self secondMethodWithArray:someArray dictionary:someDictionary andColor:someColor];
} // and so on
}
Notice how I changed sender from id to UIButton here. This enables you to call tag without casting and not get a compiler warning, because compiler know you only expect UIButton instances to call this method.
If you really wanted to you could create a separate callback for each button, like:
// In your Whatever.h file
- (IBOutlet)actionButton1:(UIButton *)sender;
- (IBOutlet)actionButton2:(UIButton *)sender;
// In your Whatever.m file
- (IBOutlet)actionButton1:(UIButton *)sender
{
// do button 1 specific stuff
}
- (IBOutlet)actionButton2:(UIButton *)sender
{
// do button 2 specific stuff
}
// etc you get the idea
Then from Interface Builder look at your Whatever.xib file. You can link the "Sent Event" (drag from right side column) of "Touch Up Inside" to any of those actions above which will pop up in "File's Owner" (left column, when you release drag). You can do a different one for each button.
I should mention that generally if these are variants of the same functionality it makes more sense to use the tag property of UIButton.
I have solved this problem with an array of objects: all the parameters are stored in one object, then the object is inserted in the array, finally, the index of the object in the array is passed in the TAG property of the button. This technique works for one or many buttons. I did it for an app that had a list of contacts, where you were able to accept or reject them with buttons, and this buttons were calling the same action method, thanks to the TAG it was possible to know what parameters send to database. Steps:
1- Create a new class : New File, Objective-C class, name it, subclass of NSOBJECT, save it.
2- In the header of this new class declare one property for each parameter.
3- Now go to the IMPLEMENTATION file of the class of the viewcontroller where your button belongs to.
4- Import your new class :
#import "new_class.h"
5- Declare the array in the INTERFACE section :
#interface my_viewcontroller ()
{ NSMutableArray * my_array; }
6- In the VIEWDIDLOAD method create the array as empty :
my_array = [ [ NSMutableArray alloc ] initWithObjects : nil ];
7- In the method where you get the data for the parameters, declare an object of the new class, instantiate it and fill the properties :
new_class * nc;
nc = [ [ new_class alloc ] init ];
nc.param1 = #"abc";
nc.param2 = 123;
nc.param3 = true;
8- Now insert it in the array :
[ my_array addObject : nc ];
9- Store the value 0 in the TAG of the button. You will use this value as index to access the parameters in the my_array[ 0 ] position.
If there are more buttons, for example, from a web service that returns JSON data, just loop through the data creating more instances of the new class and inserting them in the array. Later, for example, in a tableview with dynamic cells and a template cell with buttons, in the method CELLFORROWATINDEXPATH, you will be able to store the INDEXPATH value in the TAG of every button, so these buttons will access their own parameters.