Fastest way to load an NSComboBox from an 8000 item plist - objective-c

I have a plist that is basically a list of 8000 usernames. I load this into an NSDictionary, then an array of sorted keys (because the list isn't sorted when I get it) then loop through loading into an NSComboBox.
This works, but can take a few seconds to populate the combo box.
Here's my code:
// in my .h
IBOutlet NSComboBox *comboUserList; // which is connected to a combo box in my .xib
// in my .m
// userInfoPlist is an NSString path to the file
NSDictionary *userList = [NSDictionary dictionaryWithContentsOfFile:userInfoPlist];
// sort user info into an array
NSArray* sortedKeys = [userList keysSortedByValueUsingSelector:#selector(caseInsensitiveCompare:)];
// then populate the combo box from userList in the order specified by sortedKeys
for ( NSString *usersKey in sortedKeys) {
[comboUserList addItemWithObjectValue:[userList objectForKey:usersKey]];
}
So this works, but for 8000 odd entries it takes some noticeable time to populate the combo box (only a second or two on a 2011 MAcBook Air, but still noticeable). Is there a faster way to use either the NSDictionary or NSArray as a data source rather than do it in a for loop?

User External Data Source.
[mEmailListBox setUsesDataSource:YES];
[mEmailListBox setDataSource:self];
/*
If you use setDataSource: before setUsesDataSource:, setDataSource: throws an exception.
*/
- (NSInteger)numberOfItemsInComboBox:(NSComboBox *)aComboBox;
{
return [DatSource count];//DatSource NSArray
}
- (id)comboBox:(NSComboBox *)aComboBox objectValueForItemAtIndex:(NSInteger)index;
{
return DatSource[index];
}
Take a look at Combo Box Programming Topics
You can also load data in background with the help of noteNumberOfItemsChanged and reloadData methods

You should use a data source instead of providing the values directly. Use -[NSComboBox setUsesDataSource:] and -[NSComboBox setDataSource:] to set your datasource then implement NSComboBoxDataSource protocol on your controller.
See:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Protocols/NSComboBoxDataSource_Protocol/Reference/Reference.html#//apple_ref/occ/intf/NSComboBoxDataSource

If you do not need eact behavior like this code when you sort keys and put values into NSComboBox you can do it different.
If it is OK to put sorted keys or values you can use one call instead of looping:
[comboUserList addItemsWithObjectValues:sortedKeys];

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…

How to add imagepath inside array one by one in iphone

I am new to iPhone programming. How do I add an NSString to an NSArray. I have thumbnail images. If I click on any one of the images I want to store imagepath in the NSArray. Same if I click on the same image I want to remove the imagepath from NSArray. (like select and deselect) Can anybody tell me how I can do this, what is the logic for that? Till now I have been doing
- (void)click
{
NSLog(#"%#",_fullsizeUrl);
array =[[NSMutableArray alloc]init];
[array addObject:_fullsizeUrl];
NSLog(#"%#",array);
}
In above code _fullsizeUrl is imagepath I am storing this in an NSMutableArray. The problem is if select any one image means Ii getting image path that is storing in NSArray. But if I select any other images it's storing the latest one previous one is removing. Can any body tell me how to do this
Thanks
Your "previous one is removing" because you are reallocating your NSMutableArray everytime you tap the thumbnail. Allocate the array outside the click method.
-(void)click
{
if ([yourAry containsObject:yourObject])
{
[yourAry removeObject:yourObject];
}
else{
[yourAry addObject:yourObject];
}
}
array =[[NSMutableArray alloc]init];
[array addObject:_fullsizeUrl];
You are creating a new array every time click is called. At the end of the method, the array has no strong references (assuming ARC, otherwise you are leaking memory) and gets deallocated.
Make your NSMutableArray a property on your class and then click should look like this:
- (void)click
{
if ([self.myArray containsObject:self.fullSizeUrl]) {
[self.myArray removeObject:self.fullSizeUrl];
} else {
[self.myArray addObject:self.fullSizeUrl];
}
}
If you are only storing unique strings in your array you could consider using NSMutableSet instead.
You can use this method and use one int variable i and increase ever time +1.
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index;

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 click a checkbox in nstableview and update an object using KVC?

I am trying to learn cocoa and have a few problems with KVC and bindings. I have a nstableview with three columns; "checkbox", "text", "icon". The values of each column is binded to an arraycontroller using KVC. When program is launched the rows and columns are correctly filled into the tableview according to the values in the array. I can click a row and correctly print the content of that row using something like this:
- (IBAction)fileTableViewSelected:(id)sender{
NSInteger r;
NSDate *fModOne;
id object;
r = [[NSNumber numberWithInt:[sender selectedRow]] intValue];
object = [arrayIntersect objectAtIndex:r];
fModOne = [object valueForKey:#"fileModifiedDirOne"];
NSLog(#"Date found in row is %#",fModOne);
}
My problem is when I try to click the checkbox in column one and change the value of the box. Initially, the value of the checkbox is set to 1 using the arraycontroller which works fine, but when I want to change the value of the checkbox of a specific row to 0 by clicking on it the program crashes. When the box is clicked an action is correctly called and this is where I thought I could simply change the value of my objects BOOL by calling:
[object setValue:[NSNumber numberWithBool:NO] forKey:#"doSync"];
My setters and getters for the BOOL doSync is defined as:
#property(nonatomic, readwrite) BOOL doSync;
#dynamic doSync;
- (void)setDoSync:(BOOL) value{
NSLog(#"setting dosync %i", value);
doSync = NO;
}
- (BOOL)doSync{
return doSync;
}
I have searched everywhere for a solution to my problem, but I am unable to find any examples of how to use checkboxes in tableview using KVC and bindings. I appreciate any help I can get on this and I would appreciate any examples I could take a look at.
Cheers and thanks! Trond
You don't need to implement this yourself as an action. Just bind the column through your array controller's arrangedObjects to the doSync property of the model objects.
If you don't want to use Bindings, you still shouldn't implement it as an action. Instead, be the table view's data source and respond to the message the table view will send you to change one of the values.
#dynamic doSync;
There's no reason to have this if you turn around and implement the accessors for that property in the same class.
If this is a managed-object class and the property is an attribute of the entity, then your accessors should send [self willAccessValueforKey:] before and [self didAccessValueForKey:] after accessing the instance variable. If that's all they do, then you should not implement the custom accessors at all; cut them out and have #dynamic alone.
- (void)setDoSync:(BOOL) value{
doSync = NO;
That's not setting the property to the value passed in.

Using Array Controllers to restrict the view in one popup depending on the selection in another. Not core data based

I am working on an app that is not core data based - the data feed is a series of web services.
Two arrays are created from the data feed. The first holds season data, each array object being an NSDictionary. Two of the NSDictionary entries hold the data to be displayed in the popup ('seasonName') and an id ('seasonID') that acts as a pointer (in an external table) by matches defined for that season.
The second array is also a collection of NSDictionaries. Two of the entries hold the data to be displayed in the popup ('matchDescription') and the id ('matchSeasonId') that points to the seasonId defined in the NSDictionaries in first array.
I have two NSPopUps. I want the first to display the season names and the second to display the matches defined for that season, depending on the selection in the first.
I'm new at bindings, so excuse me if I've missed something obvious.
I've tried using ArrayControllers as follows:
SeasonsArrayController:
content bound to appDelegate seasonsPopUpArrayData.
seasonsPopup:
content bound to SeasonsArrayController.arrangedObjects; content value bound to SeasonsArrayController.arrangedObjects.seasonName
I see the season names fine.
I can obviously follow a similar route to see the matches, but I then see them all, instead of restricting the list to the matches for the season highlighted.
All the tutorials I can find seem to revolve around core data and utilise the relationships defined therein. I don't have that luxury here.
Any help very gratefully received.
This is not an answer - more an extension of the previous problem.
I created MatchesArrayController and subclassed it from NSArrayController to allow some customisation.
Following the example in 'Filtering Using a Custom Array Controller' from 'Cocoa Bindings Topics', I followed the same idea as above:
MatchessArrayController: content bound to appDelegate matchesPopUpArrayData.
matchesPopup: content bound to MatchesArrayController.arrangedObjects; content value bound to MatchesArrayController.arrangedObjects.matchDescription.
I've derived the selected item from seasonPopUp:sender and used this to identify the seasonId.
The idea is to change the arrangedObjects in MatchesArrayController by defining the following in;
- (NSArray *)arrangeObjects:(NSArray *)objects
{
if (searchString == nil) {
return [super arrangeObjects:objects];
}
NSMutableArray *filteredObjects = [NSMutableArray arrayWithCapacity:[objects count]];
NSEnumerator *objectsEnumerator = [objects objectEnumerator];
id item;
while (item = [objectsEnumerator nextObject]) {
if ([[[item valueForKeyPath:#"matchSeasonId"] stringValue] rangeOfString:searchString options:NSAnchoredSearch].location != NSNotFound) {
[filteredObjects addObject:item];
}
}
return [super arrangeObjects:filteredObjects];
}
- (void)searchWithString:(NSString *)theSearchString {
[self setSearchString:theSearchString];
[self rearrangeObjects];
}
- (void)setSearchString:(NSString *)aString
{
[aString retain];
[searchString release];
searchString=aString;
}
I've used NSLog to check that things are happening the way they are supposed to and all seems ok.
However, it still doesn't do what I want.
[self rearrangeObjects]; is supposed to invoke the arrangeObjects method but doesn't. I have to call it explicity
(i.e.[matchesArrayController arrangeObjects:matchesPopUpArrayData]; )
Even then, although filteredObjects gets changed the way it is supposed to, the drop down list does not get updated the way I want it to.