How to display data in a nscombobox in cocoa? - objective-c

I have an NSComboBox in my mainmenunib file.
I have created an outlet of combobox "cb" and made a connection of it with my delegate
I also connected delegate and datasource with my delegate.
-(void)applicationDidFinishLaunching:(NSNotification *)aNotification
{ arr=[NSMutableArray arrayWithObjects:#"a",#"b",#"c",#"d",#"e",#"f", nil];
[cb reloadData];
}
-(NSInteger)numberOfItemsInComboBox:(NSComboBox *)aComboBox{
return arr.count;
}
-(id)comboBox:(NSComboBox *)aComboBox objectValueForItemAtIndex:(NSInteger)loc{
return [arr objectAtIndex:loc];
}
But when I run the application data is not coming in combobox.
Please help me out as i am new to cocoa programming.
Thanks in advance.

Your approach seems reasonable on the face of it, though using a mutable object as an instance variable is often ill-advised (for reasons wholly unrelated to your issue here, and which we needn't get into at this stage).
There are two things that jump out as possible issues:
1) Are you using ARC? If not, arr is going to disappear from under you because -arrayWithObjects returns an autoreleased object and you have nothing retaining it. If you are using ARC (the default for new project on Lion, I believe), this doesn't apply to you. Plus I would expect you would crash, not just get no data.
2) More likely, you forgot to -setUsesDataSource:YES, which is the flag that tells NSComboBox whether to look at its data source or to use the internal contents approach that #JustinBoo supplied. I believe this defaults to NO, which would cause your exact problem. I don't have Interface Builder in front of me at the moment, but IIRC there is a "uses data source" checkbox that you can check to enable this attribute.

You can add objects using -addItemWithObjectValue to Your NSComboBox like this:
arr = [NSMutableArray arrayWithObjects:#"a",#"b",#"c",#"d",#"e",#"f", nil];
for (int i = 0; i < [arr count]; ++i)
{
[cb addItemWithObjectValue:[arr objectAtIndex:i]];
}
You can see NSComboBox Reference for more information.

Related

Cocoa Outlets acting wierd, won't recognize selector

I'm getting some weird behavior, I Set a Label in Interface Builder, then I connect the label to a file as an Referencing Outlet.
#property (weak) IBOutlet NSTextField *TitleLabel;
When I access that label in the file (cell.TitleLabel.stringValue = title) and run the application, it doesn't recognize it.I get this:
-[NSApplication TitleLabel]: unrecognized selector sent to instance 0x608000101680
The weird thing is that it doesn't always do this, sometimes it works and displays correctly, other times it doesn't.
I've just started messing with IB so I'm probably missing something. Any help?
Is the property really on your NSApplication subclass? or is it on you application delegate class? It's not impossible for it to be on the application object, but it would be a pretty uncommon (and arguably ill-advised) pattern.
In short, I suspect you're probably connecting it to the wrong object.
EDIT: Ah. I see. You're trying to access things via the topLevelObjects array, but in practice, you can't count on the order of topLevelObjects. What you need to rely on the owner's outlets getting populated, but you're passing nil for the owner. topLevelObjects only exists to give the caller "ownership" (in the reference counting sense) of the top level objects in the xib for memory-mangement purposes, it's not really meant to be "used" directly like you're doing here. (In fairness, I can imagine situations where you might need to introspect that array, but this hardly rises to that level.)
The canonical way to do this would be to use an NSViewController subclass as the owner. In Xcode, if you add a subclass of NSViewController to your project, it will give you the option to create a xib file at the same time that will have everything hooked up. Then you just initialize the NSViewController subclass at runtime and the view outlet property of that class will be filled with the root view. You can obviously add more outlets and plug in whatever you like.
This post appears to cover the basics, if your looking for more details. Apple's docs on xib files and how they work are here.
The problem was that the View would sometimes get assigned to NSApplication. I'm not sure if the way that I am initiating the view is the common way of doing it but the problem was within this block of code:
NSArray * views;
[[NSBundle mainBundle] loadNibNamed:#"CollapseClickViewController" owner:nil topLevelObjects:&views];
CollapseClickCell * cell = [[CollapseClickCell alloc] initWithFrame:CGRectMake(0,0,320,50)];
cell = [views objectAtIndex:0];
the problem was that [views objectAtIndex:0] would sometimes return NSApplication. To fix it I just checked the class against itself and returned that object via:
-(CollapseClickCell*)assignCell:(CollapseClickCell*)cell withArray:(NSArray*)array{
for ( int i = 0; i< [array count]; i++) {
if ([[array objectAtIndex:i] class] == [CollapseClickCell class]) {
return [array objectAtIndex:i];
}
}
return nil;
}
I then assign that to the object:
cell = [cell assignCell:cell withArray:views];
It may not be the conventional way of doing it but it works. If there is a better technique or a more common approach please enlighten me! :)

Updating NSTableView datasource asynchronously

I have been googling on this subject, but didn't seem able to find a consensus on the solution to this type of problem. When I use a data source with an NSTableView, if I need to populate the data source in background, there're a couple questions that pop in my mind regarding threading. I'm hoping to get some guidance here.
What would happen if I modified the data source between the main threading calling [NSTableView numberOfRowsInTableView:] and [NStableView tableView:objectValueForTableColumn:row:]? If the object the table view is asking for isn't valid anymore, what should I do?
Is making change to data source only on main thread the solution to this situation?
If 2 is the answer, does it apply to the case when binding is used?
If your data source takes some time to populate, and you're currently showing a table with older data, I think you have a couple of options:
Show a spinner over the UI while the re-population occurs, then call [tableView reloadData]
Keep the older data around so the tableView remains responsive, then once the new data has been fetched/computed, tell the datasource about the new NSArray (or whatever object holds the new data), and call [tableView reloadData].
You can't be changing the data backing your datasource on the fly, unless you inform the tableView of each item/row changing as you go.
To address the threading part, you can use a background thread to populate an NSArray of new data, once complete switch to the main thread, and on that call [dataSource setBackingArray:newStuff]; [tableView reloadData];
Many thanks to Graham Perks in comments to one of answers. This actually an answer worth to be written out explicitly. I want just to add small snippet from my project as illustration:
- (void) populateTable
{
DAL *dal = [[DAL alloc] init]; // MySQL engine
NSMutableArray *tmp = [NSMutableArray new];
NSMutableArray *records = [dal RetrieveRecordswithSql:#"select id, serial, scannerid, scans, offloaded, uploaded from scan_set_v3" withColumnsCount:#(6) andColumnsDelimiter:ScanSetRecordColumnDelimiter];
for (NSString *rec in records) {
ScanSetRecord *newRec = [[ScanSetRecord alloc] initWithScanSet:rec];
if (newRec) {
[tmp addObject:newRec];
}
}
self.dataArray = tmp;
[self.tableView reloadData];
}

Cocoa user interface loading problems

I'm a noob on objective C so I'm sorry if I don't use the right terms to describe my problem but I need a hand and SO is my least resource!
I'm trying to interface an Arduino balance with my mac with an objective C software in which I've got a Nib file and a controller one: to do that I use ORSSerialPort which runs ok.
I'm experiencing some problems while loading a NSCombobox (the one with I make the user able to choose the serial port) after the user interface loading: in my controller class I have a method called "InitializeView" which calls this method:
-(void)RefreshSerialPortsInComboBox{
//Clear all existing elements
for (int i = 0; i < [self.serialPortsComboBox numberOfItems]; i++) {
[self.serialPortsComboBox removeItemAtIndex:i];
}
//Reload the serial ports list
NSArray *availableSerialPorts = [[NSArray alloc] initWithArray:[serialPortManager availablePorts]];
//Reload the Combobox elements with the new serial ports list
for (int i = 0; i < [availableSerialPorts count]; i++) {
[self.serialPortsComboBox addItemWithObjectValue:[[availableSerialPorts objectAtIndex:i] valueForKey:#"_path"]];
[self InsertTextInBufferTextView:#"Elemento creato...\n"];
}
}
The problem is that if I call this method in my controller object's init method, this doesn't work and the combobox items list is still empty: take note that the controller is instantiated by Interface Builder.
I tried to link the above method to a button and it works so it seems to be a matter of loading priority, it seems that I'm going to call the method before loading the ui objects or making them ready to be worked on...or maybe something else but I don't know what.
Can someone help me?
Another similar alternative just for completeness is to implement the method awakeFromNib, this method is called for all objects represented in a nib file. Its defined in the informal protocol NSNibAwaking
You should call this method in loadView method:
- (void)loadView {
[super loadView];
[self RefreshSerialPortsInComboBox];
}
Your code doesn't work because view and all IBOutlets haven't not initialized in "init" method yet.

delegate not getting nil

I am working on a project in which I perform lazy loading of images. When the imagedownloader downloads the images,it sends the message to its delegate to handle the image. But when its delegate,which is a view controller, gets deallocated from memory,I dont want imagedownloader class to send messages to its delegate as its already dead. I need to know when can i set delegate of imagedownloader to nil?? My target is set to iOS4.0 so i cant use weak references. And i have many instances of imagedownloader stored in a dictionary ready to sent their delegate the completion message . I have to set delegte of all those stored instances to nil.For now i am doing
'
-(void)viewWillDisappear:(BOOL)animated
{
for(imagedownloader *imagedownloaderObj in dict)
{
imagedownloaderObj.delegate = nil;
}
[super viewWillDisAppear:animated]
}
but it crashes in the loop. Please help anyone...and sorry about my bad english but i hope you got it whats my problem..
You have a problem in your code - you are enumerating a dictionary which enumerates its keys, not its objects. Instead you want to do:
for(ImageDownloader *imageDownloader in [imageDownloaderDictionary allValues])
{
if (imageDownloader.delegate == self)
imageDownloader.delegate = nil;
} //note - I've adjusted naming to match Objective-C style conventions. It fits in better with the framework code now.
Also, I'd say to do this in dealloc instead. I don't know that you'll always get a viewWillDisappear: method before deallocating, on earlier version of iOS (including iOS4) you certainly couldn't guarantee that. And furthermore you don't want to waste time downloading the images again if you come back to that view.

Enumeration and removing a particular object from NSMutableArray

I'm having trouble removing items from my NSMutableArray. I'm extremely new to Objective-C, so please bear with me.
So far, I have the following: I'm trying to remove a line from the array if it has certain text inside. I cannot do this while fast-enumerating, so I'm trying to store the index, for removal after the enumeration has finished. However, I'm being told that this makes a pointer from an integer without a cast. Confused!
//First remove any previous Offending entry.
//Read hostfile into array.
NSString *hostFileOriginalString = [[NSString alloc] initWithContentsOfFile:#"/etc/hosts"];
NSMutableArray *hostFileOriginalArray = [[hostFileOriginalString componentsSeparatedByString:#"\n"] mutableCopy];
NSInteger hostFileOffendingLocation;
//Use a for each loop to iterate through the array.
for (NSString *lineOfHosts in hostFileOriginalArray) {
if ([lineOfHosts rangeOfString:#"Offending"].location != NSNotFound) {
//Offending entry found, so remove it.
//[hostFileOriginalArray removeObject:lineOfHosts];
hostFileOffendingLocation = [hostFileOriginalArray indexOfObject:lineOfHosts];
//NSLog(#"%#", hostFileOffendingLocation);
}
}
//Release the Array.
[hostFileOriginalArray release];
//Remove offending entry from Array.
[hostFileOriginalArray removeObject:hostFileOffendingLocation];
My real question is why are you releasing your array before modifying it
try moving
[hostFileOriginalArray release];
to after
[hostFileOriginalArray removeObject:hostFileOffendingLocation];
You can do this without the loop by calling [hostFileOriginalArray removeObjectIdenticalTo:#"Offending"];
Note that it will remove multiple instances of the offending object, but that looks like what you want anyway. It will also do the operation in a fast way, without you having to worry about the implementation detail of which loop to use.
As a general rule (especially with the really commonly used objects like containers and NSString), check the class reference to see if Apple already has a way of doing what you want to do. It makes your code more readable to other Cocoa users (including future you), and reduces code maintenance- you're now leaving it up to Apple to add new technologies like Fast Enumeration to their code, and you get it for free when you link against new versions of the SDK.
Also, you should probably return hostFileOriginalArray at the end of the function, so it can do something useful- you can return it as an autoreleased object.
//Remove offending entry from Array.
[hostFileOriginalArray removeObjectAtIndex:hostFileOffendingLocation];
//Release the Array.
[hostFileOriginalArray release];
you should have been getting compiler warnings... take a look at them, they are usually very helpful, I always try to have 0 warnings... that way I know where I have done something careless.