Creating Toolbars for Mac OS X
Pages: 1, 2, 3, 4
Searching
Getting the search item to function requires some heavy modification to the way records are stored in the address book application. Rather than lead you through a detailed, step-by-step description of every change that was made, I will give you an overview of what’s going on and leave you with the code to download and study. The project folder that has all of the additions made can be downloaded here.
As it stands now, the individual records are stored in an instance of NSMutableArray assigned to the instance variable records. Whenever information is displayed in a tableview, records is the provider of that information via the NSTableDataSource methods.
Now, suppose we want to restrict the information displayed in the tableview to some subset of records based on the search criteria. How would we go about obtaining and using a subset of records as a data source without modifying the contents of records? The way we go about this is to introduce two more NSMutableArray instance variables called subset and activeSet. To see where activeSet occurs in the code, do a search for it in the Find panel.
Rather than using the variable records in the data source methods, we use activeSet. The trick is that the NSMutableArray object assigned to activeSet changes depending on the function being performed. When the application loads, activeSet is set to equal records immediately after a mutable array has been assigned to records. In this way both activeSet and records point to the same object in memory. Two variables, one object.
Now when we perform a search, we create another instance of NSMutableArray and assign it to the variable subset. The search will consist of enumerating records and comparing the first and last name strings in the record dictionary to the search string. If there is a partial match, then that record is added to subset. After records has been completely enumerated, we assign activeSet to subset, and the tableview will then display the contents of subset -- that is, the search results. When the search field is cleared, activeSet is once again set to equal records.
|
Also in Programming with Cocoa |
Understand now that when we speak of adding objects to subset, we’re actually adding the pointers to the particular objects to subset, and subset is sending retain messages to those objects. So now both records and subset have ownership over a shared set of objects. This is the same “two variables, one object” situation we had above, except that the variables are elements of subset and records. This is significant to the way the tableview works. In particular, when you edit information directly in the table, that information is not changed solely in records contained in subset (via activeSet); the changes are also reflected in object.
Here again is how it works: When records is first initialized, we set activeSet equal to it. When we search we assign activeSet to subset. Finally, in the search method we will have an if-statement checking to see if the length of the search string is 0. If so, then we reassign activeSet to records.
So that’s what’s going on with our change of variable names and introduction of new instance variables. How do we make the search function work? Recall that in Interface Builder we set NSController to be the delegate of the text field in searchItemView. The reason we did this is so we could implement a useful delegate method of NSControl called -controlTextDidChange:, which is invoked whenever the text in the text field changes. Thus, a new search will be performed after each letter is typed in the search field.
Add to Controller.m the following definition of -controlTextDidChange:
- (void)controlTextDidChange:(NSNotification *)aNotification
{
NSString *searchString = [[[aNotification object] stringValue] lowercaseString];
NSEnumerator *e = [records objectEnumerator];
NSString *fnString, *lnString;
id object;
if ( [searchString length] == 0 ) {
activeSet = records;
[tableView reloadData];
return;
}
[subset release];
subset = [[NSMutableArray alloc] init];
while ( object = [e nextObject] ) {
fnString = [[object objectForKey:@"First Name"] lowercaseString];
lnString = [[object objectForKey:@"Last Name"] lowercaseString];
if ( [fnString hasPrefix:searchString] || [lnString hasPrefix:searchString] )
[subset addObject:object];
}
activeSet = subset;
[tableView reloadData];
}
What we have here is an enumerator for records that checks every record contained in that array to see if the search string is a prefix of either the first name value or the last name value. If so, we add it to subset and continue enumerating. Once the enumeration has completed we set activeSet to point to subset and then tell tableView to reload its data.
There are a couple of things to note here. First, we make the search case insensitive by sending lowercaseString messages to all of the strings involved and use the returned strings in the actual search.
Additionally, note the argument of this method, NSNotification. This is a class which we have not discussed yet in this series of columns. If you read the class documentation on this method in the NSControl and NSTextField specifications you will see that the controlTextDidChange: method is called in the delegate by a notification. A notification is a way of communicating between objects who have no knowledge of each other using a broadcast paradigm. The way it works is that objects can register themselves with a notification center, which is an instance of the class NSNotificationCenter. By registering itself in this way, an object becomes an observer which responds to only certain named notifications. In this registration process the object indicates which one of its methods will be invoked in response to the notification.
You register your object with the default notification center using the method -addObserver:selector:name:object:. Here observer is the object to respond to the notification, selector is the method to be invoked in the observer, name is the name of the notification which observer shall respond to, and the object argument is used to restrict which notifications the observer can respond to. To respond to all notifications with the given name, pass nil as this argument. Otherwise, you can have your observer respond only to notifications that have been posted by an object indicated in this argument.
Conveniently, by assigning NSController as the delegate of the search text field, this has all been taken care of for us behind the scenes. All we had to do was implement -controlTextDidChange:.
The other side of the story with notifications is the object posting a notification. Objects post notifications to the notification center using either the -postNotification: or the -postNotificationName:object: of NSNotificationCenter. In the second method object is often self. When a notification is posted to the notification center it is then broadcast to all observers of that notification. The notification center acts as a middle man in this one-way communication between two stranger objects.
So, back to our search method. The object which sent the notification can be accessed by passing an object message to the notification object. This is how we gain access to the text field whose contents have changed and retrieve the string contained within.
Now, with this method added to your code, and the needed changes made as described above, you should have a cool little Search function in your application.
This concludes learning how to make toolbars. As always, I highly recommend reading all of the relevant class documentation as I have only touched on a few possibilities. See you next time!
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 31 of 31.
-
My customized items are disabled
2008-07-16 03:03:16 meharoof [Reply | View]
I created a toolbar containing two items, contact and compose.I created contact window and compose window. On clicking of toolbar items i want to open the appropriate window and I succeeded in that. But my problem is, At the program launching time toolbar items are enabled and when I clicking any one item the appropriate window will open and cause all the toolbar items disabled. How can I solve the problem?
-
Cocoa document-based app with toolbars
2008-05-07 04:50:27 johnlove [Reply | View]
I really have enjoyed Mike's tutorial and it definitely works with a straight Cocoa app ... but when I switch to a document-based app ... well, I am having some difficulty:
As you know, with a document-based app, the menu exists in the MainMenu.nib and the window exists in the MyDocument.nib. When I create a new NSObject, called "MyToolbar", where do I put it ..
(a) if in the MainMenu.nib along with the menu, how do I connect the window outlet of MyToolbar = "window" to the title bar of the window which is in the MyDocument.nib?
(b) if in the MyDocument.nib, I've got a parallel problem with the menu in the MainMenu.nib.
I am reasonably confident that I am displaying ny ignorance about something very basic, but I really do need your help.
Thanks,
John Love
-
Sizing toolbarItems after window/panel resize
2007-03-09 07:42:45 slashlos [Reply | View]
Good tutorial; I believe I've digested this somewhat but want to see it continued regarding resizing. I'd like my searchView to widen as the panel/display widens, so I added this to my views delegate:
- (void)windowDidResize:(NSNotification *)note
{
NSRect windRect = [window frame];
NSRect searchRect = [searchView frame];
searchRect.size.width = windRect.size.width;
[searchItem setMinSize:searchRect.size];
[searchItem setMaxSize:searchRect.size];
[searchView setFrame:infoRect];
[searchView setNeedsDisplay:YES];
}
The view sizes but its display gets clipped to its original size? My search view has more stuff in it than a single searchField all contained in an NSBox but I am trying to have the whole thing grow/shrink as the window does?
--
/los "I was a teenage net-random"
-
Small error in setupToolbar method
2004-07-13 14:00:39 krishengreenwell [Reply | View]
As mentioned here:
http://www.cocoadev.com/index.pl?NSToolbarSampleCode
...there's a small error with the provided setupToolbar method. The correct code is:
- (void)setupToolbar
{
NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"mainToolbar"];
[toolbar autorelease]; // wrong
[toolbar setDelegate:self];
[toolbar setAllowsUserCustomization:YES];
[toolbar setAutosavesConfiguration:YES];
[mainWindow setToolbar:[toolbar autorelease]];
}
As you can see, there is a double autorelease, which is incorrect; it will cause the program to crash. The first autorelease call should be removed. -
Small error in setupToolbar method
2004-07-13 14:07:24 krishengreenwell [Reply | View]
Pardon... now there's an error with my error report. The code listed was the *incorrect* code, not the corrected version. It should have read:
---------------------------------
As mentioned here:
http://www.cocoadev.com/index.pl?NSToolbarSampleCode
...there's a small error with the provided setupToolbar method:
- (void)setupToolbar
{
NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"mainToolbar"];
[toolbar autorelease]; // wrong
[toolbar setDelegate:self];
[toolbar setAllowsUserCustomization:YES];
[toolbar setAutosavesConfiguration:YES];
[mainWindow setToolbar:[toolbar autorelease]];
}
As you can see, there is a double autorelease, which is incorrect; it will cause the program to crash. The first autorelease call should be removed.
---------------------------------
-
Hey Can i have some info please?
2003-07-06 21:30:56 anonymous2 [Reply | View]
Hey there, im curious i've started to write an OS X application for students and i was surprised because when i finished the tutorial i realized it would be the almost perfect start to an app im trying to write. Anyway im curious as to what the polocy is about the code for this project? Obviously they've published the code and given people the finished project. So whats the deal with this project can i go take pieces of the code and use it or use this project as the foundation for a much bigger project than what this app does? Im just trying to get a feel for the polocy here at OReilly about these tutorials, because i don't want to violate any copyrights anyone may have. Some parts of this project seem basic and not really much more than basic cocoa stuff, but again i just don't want to get in any trouble.
-
My customized items are disabled...
2002-12-24 22:36:55 anonymous2 [Reply | View]
I am trying to make my own app based on this... All that is done so far is this, but now my customized icons are disabled... Here is my code:
- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
itemForItemIdentifier:(NSString *)itemIdentifier
willBeInsertedIntoToolbar:(BOOL)flag {
NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
if ( [itemIdentifier isEqualToString:@"Finder"] ) {
[item setLabel:@"Finder"];
[item setPaletteLabel:[item label]];
[item setImage:[NSImage imageNamed:@"Finder.icns"]];
[item setTarget:self];
[item setAction:@selector(finderprefs:)];
} else if ( [itemIdentifier isEqualToString:@"Background"] ) {
[item setLabel:@"Background"];
[item setPaletteLabel:[item label]];
[item setImage:[NSImage imageNamed:@"ScreenEffects.tiff"]];
[item setTarget:self];
[item setAction:@selector(screeneffectsprefs:)];
}
return [item autorelease];
}
- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar {
return [NSArray arrayWithObjects:NSToolbarSeparatorItemIdentifier,
NSToolbarSpaceItemIdentifier,
NSToolbarFlexibleSpaceItemIdentifier,
NSToolbarCustomizeToolbarItemIdentifier,
@"Finder", @"Background", nil];
}
- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar {
return [NSArray arrayWithObjects:@"Finder", @"Background",
NSToolbarFlexibleSpaceItemIdentifier,
NSToolbarCustomizeToolbarItemIdentifier, nil];
}
- (void)setupToolbar {
NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"mainToolbar"];
[toolbar autorelease];
[toolbar setDelegate:self];
[toolbar setAllowsUserCustomization:YES];
[toolbar setAutosavesConfiguration:YES];
[mainWindow setToolbar:toolbar];
}
- (void)screeneffectsprefs {
viewSize = [screenEffectsView frame].size;
[mainWindow setContentView:mainView];
[mainWindow setContentSize:viewSize];
[mainWindow setContentView:screenEffectsView];
}
- (void)finderprefs {
viewSize = [finderView frame].size;
[mainWindow setContentView:mainView];
[mainWindow setContentSize:viewSize];
[mainWindow setContentView:finderView];
}
-
Double autorelease
2002-11-04 19:25:57 anonymous2 [Reply | View]
On page 2, the code says:
- (void)setupToolbar
{
NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"mainToolbar"];
[toolbar autorelease];
[toolbar setDelegate:self];
[toolbar setAllowsUserCustomization:YES];
[toolbar setAutosavesConfiguration:YES];
[mainWindow setToolbar:[toolbar autorelease]];
}
But that sends toolbar two autorelease messages. Oh the EXC_BAD_ACCESSes. :) I figured the code was right, so I left it, until I thought about it a bit, then noticed that the first message wasn't mentioned in the analysis. Anyway, removing the first -autorelease call will fix it all.
-
Customize menu item disabled
2002-03-31 13:19:59 zeus [Reply | View]
Hello everyone,
I'm a bit late on this tutorial.
I try to finish the first part of it before customization and my problem is that the "Customize Toolbar" menu item is disabled (when I'm running the soft or even when I'm testing the interface in Interface Builder).
Does anyone know how to solve the problem? -
Customize menu item disabled
2002-04-05 18:41:52 michele [Reply | View]
Hello Zeus (my God!:-),
It's perfectly normal that the item is disabled in Interface Builder as the toolbar is not initialized.
In your code, in Controller.m, you have to set up the toolbar.
in awakeFromNib:
[self setUpToolbar];
and then add the following method in Controller.m:
- (void) setupToolbar
{
NSToolbar *toolbar = [[[NSToolbar alloc] initWithIdentifier: @"mainToolbar"] autorelease];
[toolbar setDelegate: self];
[toolbar setAllowsUserCustomization: YES];
[toolbar setAutosavesConfiguration: YES];
[mainWindow setToolbar: toolbar];
}
In controller.h , declare it:
- (void) setupToolbar;
And don't omit to implement the toolbar category.
Hope this helps you,
Michèle -
Customize menu item disabled
2002-04-06 03:28:17 zeus [Reply | View]
God is grateful to you humble mortal ;-)
and God should learn reading before posting stupid thread.
But now that everything should work I get a stupid:"AddressBook.app has exited due to signal 10 (SIGBUS)."or "AddressBook.app has exited due to signal 11 (SIGSEGV)." It depends on where I put my [self setupToolbar] in the awakeFromNib method.
CU Jerome -
Customize menu item disabled
2002-04-07 12:33:53 michele [Reply | View]
Hello Jerome,
I put it at the very end of the awakeFromNib method.
Is your code compiling, or do you get the error on building?
Those errors normally are thrown when you attempt to get an object with null address: i.e. an object already released (either by you or the autorelease pool), an object that does not exist yet, ...
If the code compiles, you can try to put a breakpoint at the very beginning of the awakeFromNib method, or init method, and go through the debugger step by step, check as many object as you can, printing the description by control-clicking on the object in the debugger.
Another way is to put a new breakpoint and type directly -[NSException raise] so that the debugger breaks when an exception is raised, then you back trace.
Also you can set a new variable in executable part of the target:
NSZombieEnabled
Set it to YES. Then the debugger prints a message when you attempt to call an object already released.
We can follow this discussion out of the forum. My email address is: garoche.michele@wanadoo.fr
Michele
-
Enabling / Disabling toolbar items and buttons
2002-03-20 23:32:00 michele [Reply | View]
Finally, I've made some changes to enable / disable buttons and toolbars items in case of no selected records or selected records with no email addresses.
First I've added three outlets in IB for Add button, Delete button, and Send Mail button, and connected them to the controller.
Then I've declared the controller as delegate for the tableView in IB so that it responds to the tableViewSelectionDidChange method.
And I've added two methods:
- (void) tableViewSelectionDidChange: (NSNotification *) aNotification
{
[insertButton setEnabled: (([tableView numberOfSelectedRows] == 0) ? NO : YES)];
[deleteButton setEnabled: (([tableView numberOfSelectedRows] == 0) ? NO : YES)];
[sendMailButton setEnabled: [self validateSelectedEmails]];
[insertButton setNeedsDisplay: YES];
[deleteButton setNeedsDisplay: YES];
[sendMailButton setNeedsDisplay: YES];
}
so that the buttons are enabled or disabled according to selected records.
- (BOOL) validateSelectedEmails
{
NSEnumerator *e;
NSString *selectedEmailString;
NSNumber *index;
BOOL flag = YES;
// Check if there are selected rows
if ([tableView numberOfSelectedRows] == 0)
{
flag = NO;
return flag;
}
// Enumerate all selected rows
e = [tableView selectedRowEnumerator];
// Traverse the selected rows with enumerator
while ( ( index = [e nextObject] ) )
{
// Retrieve the selected records
if ([index intValue]<= [activeSet count] - 1)
{
selectedEmailString = [[activeSet objectAtIndex: [index intValue]] objectForKey: @"Email"];
if ([selectedEmailString isEqualToString: @""])
{
flag = NO;
return flag;
}
}
}
return flag;
}
and changed the following category method:
- (BOOL) validateToolbarItem: (NSToolbarItem *) anItem
{
if ([anItem action] == @selector(deleteRecord:))
{
return ([tableView numberOfSelectedRows] > 0);
}
else if ([anItem action] == @selector(insertRecord:))
{
return ([tableView numberOfSelectedRows] > 0);
}
else if([anItem action] == @selector(sendMail:))
{
return ([self validateSelectedEmails]);
}
else if ([[anItem label] isEqualToString: @"Search Records"])
{
[anItem setEnabled: ([records count] > 0)];
return ([records count] > 0);
}
return YES;
}
As for the dimmed colors, it's the fault of my poor eyes. As I'm a little colour-blind, I cannot see well changes of colours, unless I set contrast and luminosity to their maxima, which is not good in turn for my eyes;
Missing are methods to check valid names, valid email addresses and valid phone numbers, print the whole thing as a booklet, then a script to retrieve the already stored addresses in an old application (not even carbonized).
Well, when finished, it wil be the first useful (for me) application I ever wrote.
Many thanks to Mike and all fellows.
Michèle
-
fixed vanishing ap, now doesn't vanish
2002-03-20 23:04:00 psheldon [Reply | View]
setuptoolbar had too many [toolbar autorelease]'s (two).
-
mainWindow not in your code
2002-03-20 21:35:21 psheldon [Reply | View]
At least not in original project. I didn't use the link you put in the current column. I added mainWindow as an outlet in controller and modified controller.h to declare this IB outlet of type NSWindow*. I'm getting my ap vanishing on launch, sometimes.
I'll continue reading the column and putting in code, but I might not see what is going on.
;-)
When storytelling festival in Denton is over, I may have to download Mike's starting point for this article so I won't go off on a tangent and start "tabla rasa" or "redo". Didn't ever try a redo in cocoa. Redo's are much easier than first time around because the major work is making decisions involved in comprehending the reading and writing the code.
So, for now, is the starter project different from where we left off?
-
Synchronization
2002-03-20 20:16:20 michele [Reply | View]
Below a solution (maybe not the finest one) to get synchronization between records and display.
First a validateRecord method to avoid empty records called before adding and inserting records:
- (BOOL) validateRecord
{
if ([[firstNameField stringValue] isEqualToString: @""])
{
[firstNameField setStringValue: @"Enter a first name, please"];
[firstNameField selectText: self];
return NO;
}
else if ([[lastNameField stringValue] isEqualToString: @""])
{
[lastNameField setStringValue: @"Enter a last name, please"];
[lastNameField selectText: self];
return NO;
}
return YES;
}
Better solution would be to test for having only printable characters, but for now I don't know how to do that. Plus test for a valid email address if any. Any hint?
Then some changes in the deleteRecord method (second part): I've introduced a dummy string for replacing all objects to be deleted by this special string. It is the only way I've found not to delete identical records, as I do not test if a new record is identical to an already stored record before saving it.
Better solution would be to alert the user when a record identical to another is about to be saved.
Then I've added an outlet to the searchText field, to retrieve it easily in any case (not just when the user changes its content).
I've changed the controlTextDidChange method and added three other methods for synchronizing records and selected records in any case. Hence I've changed the addRecord, insertRecord, sendMail, deleteRecord methods accordingly.
- (IBAction) addRecord: (id) sender
{
// Check the validity of record fields
if ([self validateRecord])
{
// Call create record to create the dictionary
// Then adds the dictionary to the end of the array
// The array retains the dictionary
[records addObject: [self createRecord]];
// Synchronize display
[self synchronizeView];
// Update the contents of the table after changes
[tableView reloadData];
// Save data to file
[self saveData];
// Clear the fields
[self clearField];
}
}
// Delete with a sheet - second part
- (void) sheetDidEnd: (NSWindow *) sheet
returnCode: (int) returnCode
contextInfo: (void *) contextInfo
{
NSEnumerator *enumerator; // For enumerating the selected rows
NSNumber *index; // For the NSNumber returned by nextObject
int realIndex;
NSString *delete = @".";
// Remove if OK
if ( returnCode == NSAlertDefaultReturn )
{
// Creates the enumerator
enumerator = [tableView selectedRowEnumerator];
// Traverse the enumerator
while ( (index = [enumerator nextObject]) )
{
// Retrieves the object to be deleted
realIndex = [records indexOfObjectIdenticalTo:[activeSet objectAtIndex: [index intValue]]];
// Replace the object at index with null value
[records replaceObjectAtIndex: realIndex withObject: delete];
}
// Remove all objects identical to delete
[records removeObject: delete];
// Deselect the rows
[tableView deselectAll: self];
// Synchronize display
[self synchronizeView];
// Updates the table contents
[tableView reloadData];
// Save data to file
[self saveData];
}
}
- (IBAction)insertRecord:(id)sender
{
// Validate the record
if ([self validateRecord])
{
// Retrieve the index of the selected row in the display view
int index = [tableView selectedRow];
if (index != -1)
{
// Retrieve the corresponding index in the records file
int realIndex = [records indexOfObjectIdenticalTo:[activeSet objectAtIndex: index]];
// Insert the dictionary before the selected row
[records insertObject: [self createRecord] atIndex: realIndex];
// Synchronize display
[self synchronizeView];
// Update the table contents
[tableView reloadData];
// Save data to file
[self saveData];
// Clear the fields
[self clearField];
}
}
}
// For sending mail
- (IBAction) sendMail: (id) sender
{
NSMutableString *url = [NSMutableString stringWithString: @"mailto:"];
NSString *str;
NSEnumerator *enumerator;
NSNumber *index;
int realIndex;
// Check if there is a selected row
if ([tableView numberOfSelectedRows] == 0)
{
return;
}
// Enumerate all selected rows
enumerator = [tableView selectedRowEnumerator];
// Traverse the selected rows with enumerator
while ( ( index = [enumerator nextObject] ) )
{
// Retrieve the email field
realIndex = [records indexOfObjectIdenticalTo:[activeSet objectAtIndex: [index intValue]]];
str = [[records objectAtIndex: realIndex]
objectForKey: @"Email"];
// Add a comma to separate email addresses if this is not the first one
if (![url isEqualToString: @"mailto:"])
{
[url appendString: @","];
}
// Add the email address to the previously built string
[url appendString: str];
}
// Send the whole string to the application declared as email client
[[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: url]];
}
- (void) controlTextDidChange: (NSNotification *) aNotification
{
NSString *searchString = [[[aNotification object] stringValue] lowercaseString];
NSMutableArray *selectedRecords = [self retrieveSelectedRecords];
[self synchronizeRecord: searchString];
[self synchronizeSelection: selectedRecords];
[tableView reloadData];
}
- (void) synchronizeView
{
NSString *searchString = [[searchText stringValue] lowercaseString];
NSMutableArray *selectedRecords = [self retrieveSelectedRecords];
[self synchronizeRecord: searchString];
[self synchronizeSelection: selectedRecords];
}
- (NSMutableArray *) retrieveSelectedRecords
{
NSMutableArray *tempArray;
NSEnumerator *e;
NSNumber *index;
unsigned realIndex;
// Create a temporary array to store the selected records
tempArray = [NSMutableArray array];
// Enumerate all selected rows
e = [tableView selectedRowEnumerator];
// Traverse the selected rows with enumerator
while ( ( index = [e nextObject] ) )
{
// Retrieve the selected records
if ([index intValue]<= [activeSet count] - 1)
{
realIndex = [records indexOfObjectIdenticalTo: [activeSet objectAtIndex: [index intValue]]];
// Store the object into the temporary array
[tempArray addObject: [records objectAtIndex: realIndex]];
}
}
return tempArray;
}
- (void) synchronizeRecord: (NSString *) aString
{
NSEnumerator *e = [records objectEnumerator];
NSString *fnString, *lnString;
id object;
if ([aString length] == 0)
{
activeSet = records;
return;
}
[subset release];
subset = [[NSMutableArray alloc] init];
while (object = [e nextObject])
{
fnString = [[object objectForKey: @"First Name"] lowercaseString];
lnString = [[object objectForKey: @"Last Name"] lowercaseString];
if ([fnString hasPrefix: aString] || [lnString hasPrefix: aString])
{
[subset addObject: object];
}
}
activeSet = subset;
}
- (void) synchronizeSelection: (NSMutableArray *) anArray
{
NSEnumerator *e;
id anObject;
[tableView reloadData];
// Enumerate the stored objects
e = [anArray objectEnumerator];
// Deselect all rows
[tableView deselectAll: self];
// Reselect the previously selected rows
while (anObject = [e nextObject])
{
// ... if they exist in the current subset
if ([activeSet indexOfObjectIdenticalTo: anObject] != NSNotFound)
{
[tableView selectRow: [activeSet indexOfObjectIdenticalTo: anObject] byExtendingSelection: YES];
}
}
}
Besides of things already mentioned, It remains to have the insert and delete controls disabled when no row is selected, and to dim the corresponding text or image in toolbar accordingly. The first one I'll guess I could do it by registering the controller with selectionDidChangeNotification, the second one I still have no idea how to do it.
Any comment to this soap would be greatly appreciated.
Michele
-
Setting toolbars
2002-03-20 13:00:38 joaquimlopes [Reply | View]
I always read the Programming With Cocoa articles with interest and have learned a few things with them. However I find the method for setting the toolbars that was presented to be very convoluted (too many ifs). For my current project, a students management application, I came up with what I believe is a simpler way. Here it is:
- (NSToolbarItem *)toolbar: (NSToolbar *)toolbar itemForItemIdentifier: (NSString *)itemIdent willBeInsertedIntoToolbar:(BOOL) willBeInserted
{
// Required delegate method Given an item identifier, self method returns an item
// The toolbar will use self method to obtain toolbar items that can be displayed in the customization sheet, or in the toolbar itself
NSToolbarItem *toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdent] autorelease];
/* The MEDocumentManager is an object that I created that does many things, in one of them it reads a plist
called Ferramentas (the portuguese word for tools) that is a dictionary of dictionaries where the item
identifier for tool is the key, like this (this is an example for a tool called Actualizar that uses an image
called - what else - Actualizar, and has a Tag - 9 -):
<dict>
<key>Actualizar</key>
<dict>
<key>Image</key>
<string>Actualizar</string>
<key>Label</key>
<string>Actualizar</string>
<key>Tag</key>
<integer>9</integer>
<key>Tooltip</key>
<string>Actualiza os dados</string>
</dict>
</dict>
*/
NSDictionary *tools = [[MEDocumentManager sharedDocumentManager] toolsDictionary: itemIdent];
if(tools == nil) // Just checking, for sanity sake
{
toolbarItem = nil;
}
else
{
// Set the text label to be displayed in the toolbar and customization palette
[toolbarItem setLabel: [tools objectForKey:@"Label"]];
[toolbarItem setPaletteLabel: [tools objectForKey:@"Label"]];
// Set up a reasonable tooltip, and image
[toolbarItem setToolTip: [tools objectForKey:@"Tooltip"]];
[toolbarItem setImage: [NSImage imageNamed: [tools objectForKey:@"Image"]]];
// Tell the item what message to send when it is clicked
[toolbarItem setTarget: self];
[toolbarItem setAction: @selector(toolbarItemAction:)];
[toolbarItem setTag: [[tools objectForKey:@"Tag"] intValue]];
}
return toolbarItem;
}
The toolbarItemAction method then just looks like this:
- (void)toolbarItemAction: (id)item
{
switch([item tag])
{
case whateverTag:
[self doWhatever];
break;
// Other cases...
}
}
And the validateToolbarItem method looks like this
- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
{
// Optional method self message is sent to us since we are the target of some toolbar item actions
// (for example: of the save items action)
BOOL enable = NO;
switch([toolbarItem tag])
{
case whateverTag:
enable = YES;
break;
// Other cases...
}
return enable;
}
I hope someone finds this helpful. At least for me this has saved me a lot of time and has enabled me to change a few things without changing a line of code.
Joaquim -
Setting toolbars
2002-03-25 17:36:12 Michael Beam |
[Reply | View]
Yeah, i agree that when you have a large number of items that some other management scheme would be appropriate than the if statements. I picked the method i did just to remove a layer of complexity, that is, i would have had to define the toolbar items somewhere. But as you pointed out, there's more than one way of doing it.
Mike -
Setting toolbars
2006-07-27 04:54:49 sajid_hayat [Reply | View]
Problem one
Rotation
I am trying to add some feature to the Sketch demo that will give it the ability to rotate object . I am having trouble in getting it to display the rotations correctly. When trying to draw the rotation I use an fineTransform and set just the rotateByDegrees: method. Doing this only seems to transform the coordinates within the bounds of the graphic, so it is rotation within the bounds, instead of rotating the bounds.
Problem two
Pencil
we are not able to add pencil feature into sketch demo.
-
Search method??
2002-03-18 17:05:29 michele [Reply | View]
It seems to me that the search method as implemented is not so useful and dangerous.
In fact, if I search for "toto" then I find "toto" if it exists, but thereafter there is no more synchronization between the record I click and the corresponding index in the whole (with all records) array.
So if I attempt to delete the selected record (say the fourth one, which is the twentieth in the whole array) or to insert a record without changing the search string, I don't delete the record I've selected (truly the 20th), but the record in the whole array whose index is the same as the record in the subset (the 4th); the same for inserting. Very dangerous.
If I changed the search string so that the textfield is empty, I obtain the whole array, but the selected record is no more the one I selected when I saw the subset. So, it's not very useful.
I've searched a way to change the behaviour, maybe with a combobox to allow selecting the different visible columns and a new tableview, and then swap the two tableviews as needed, to gain the same benefit as in Mail when searching a folder for a message. By now, those are only ideas, maybe not good ones.
Any hint?
Michele
-
A few questions
2002-03-18 16:49:28 michele [Reply | View]
1 - I've searched how to dim the icon when the toolbar item validate method returns no, as it is in Mail for example.
Any idea how to do that?
2 - If I would to have the search item (hence the corresponding textfield) disabled when there is no record, would I subclass NSToolbarItem to override the validate method? Hence changing completely the actual implementation?
Michele
-
A few questions
2002-03-22 12:24:33 xyzzy-xyzzy [Reply | View]
I don't understand how to disable the search item either. I've tried changing this function (from the article) to this:
- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem
{
return NO;
}
and it disables the add and remove, but not the search item (or the customize button, but that makes sense).
Is there another function I just don't know about yet? -
A few questions
2002-03-20 13:07:05 joaquimlopes [Reply | View]
Michele,
just override the validateToolbarItem in your NSWindowController and return NO when there is no record. Something like this:
- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
{
return [[self document] numberOfRecords] > 0;
}
Hope this helps.
Joaquim Lopes
-
Some changes to code
2002-03-18 16:43:47 michele [Reply | View]
Hello Mike and nice fellows,
A very good article to deal with !!!
As usual, some changes in code.
In text, following code breaks into debugger (toolbar released twice), when clicking on customization icon or hitting the corresponding menu item:
- (void) setupToolbar
{
NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier: @"mainToolbar"];
[toolbar autorelease];
[toolbar setDelegate: self];
[toolbar setAllowsUserCustomization: YES];
[toolbar setAutosavesConfiguration: YES];
[mainWindow setToolbar: [toolbar autorelease]];
}
In order, to have the toolbar visible at launch time, I've changed the code to:
- (void) setupToolbar
{
NSToolbar *toolbar = [[[NSToolbar alloc]
initWithIdentifier: @"mainToolbar"] autorelease];
[toolbar setDelegate: self];
[toolbar setAllowsUserCustomization: YES];
[toolbar setAutosavesConfiguration: YES];
[mainWindow setToolbar: toolbar];
}
Instead of defining / implementing the (void) setupToolbar method in the ToolbarDelegateCategory.h / .m, I've implemented it directly in the Controller to avoid warning message (NSController does not respond to setupToolbar method).
In the validating toolbar menu item section, the following code generates a warning message when building (no explicit return when the action is not delete records):
- (BOOL) validateToolbarItem: (NSToolbarItem *) theItem
{
if ([theItem action] == @selector(deleteRecord:))
{
return ([tableView numberOfSelectedRows] > 0);
}
}
To avoid warning, I've changed the code to:
- (BOOL) validateToolbarItem: (NSToolbarItem *) theItem
{
if ([theItem action] == @selector(deleteRecord:))
{
return ([tableView numberOfSelectedRows] > 0);
}
return YES;
}
And I've found a not too elegant way to resize the columns automatically at launch time and when the user changes the items displayed in the tableView to fit the whole area:
- (void) initializeTable: (NSTableView *) aTableView withColumns: (NSArray *) identifiers
{
NSEnumerator *e;
id identifier, column;
NSTableColumn *aTableColumn;
// Added code
float totalSize = 0.0;
// Clear out the existing columns in table view
e = [[aTableView tableColumns] objectEnumerator];
while ( ( column = [e nextObject] ) )
{
[aTableView removeTableColumn: column];
}
// Add columns from the argument array
e = [identifiers objectEnumerator];
while ( ( identifier = [e nextObject] ) )
{
aTableColumn = [tableColumns objectForKey: identifier];
[aTableView addTableColumn: aTableColumn];
[[checkBoxes objectForKey: identifier] setState: NSOnState];
// Added code
[aTableColumn setMinWidth: 200.0];
[aTableColumn setWidth: [aTableColumn minWidth]];
[aTableColumn setMaxWidth: 1000.0];
totalSize += [aTableColumn width];
}
// Resize the column to fill the whole area
if (totalSize < [aTableView frame].size.width)
{
[aTableView sizeLastColumnToFit];
}
// End of added code
}
- (void) saveTableColumnPrefs
{
id column;
NSEnumerator *e = [[tableView tableColumns] objectEnumerator];
// Added code
float totalSize = 0.0;
// Clear out the array
[userColumns removeAllObjects];
// Repopulate the array with the identifiers of the columns currently contained in the table
while ( ( column = [e nextObject] ) )
{
[userColumns addObject: [column identifier]];
// Added code
[column setMinWidth: 200.0];
[column setWidth: [column minWidth]];
[column setMaxWidth: 1000.0];
totalSize += [column width];
}
// Resize the column to fill the whole area
if (totalSize < [tableView frame].size.width)
{
[tableView sizeLastColumnToFit];
}
// End of added code
// Associate userColumns with the object at key User Columns
[prefs setObject: userColumns forKey: @"User Columns"];
}
Michele
-
Older Articles
2002-03-18 03:31:16 xxtralarge [Reply | View]
I wanted to ask a question about the animation article, but didn't know if it would be appropriate to ask here. How often do you check older articles for questions? -
Older Articles
2002-03-25 17:32:35 Michael Beam |
[Reply | View]
I don't check older articles very often, so as far as i'm concerned post comments in the newest columns. Also, don't hesitate to email me if you have a pressing question.
Mike
-
drag and drop controls
2002-03-16 12:27:59 parnet [Reply | View]
Why aren't controls drag and drop or plug and play
via the palette or are they? My hope had been that with the new toolbar architecture I would see a lot of controls to customize a lot of applications simply by drag and drop of a control to the appropriate palette and selection for the toolbar when desired. Doesn't seem to be happening. -
drag and drop controls
2002-03-17 11:39:07 Michael Beam |
[Reply | View]
Are you refering to the ability to drag controls from the customization palette to the toolbar, or to add third-party controls to the application's customization palette. Cocoa toolbars allow a user to to drag and drop toolbar controls from the palette to the toolbar, but do not allow sharing of controls between applications.
Now, it would be possible for a developer to design and implement a toolbar item plugin specification which anyone could implement and thus add custom controls to an application's repertoire of toolbar items. One of the upcoming columns will show how Cocoa's plugin architecture works, and how simple it is to define and support a plugin specification. With that knowledge it should be too difficult ot set up one for your application to add toolbar items.
This is convenient for us developers, but i agree, that it would be pretty nice to have this kind of support built into Cocoa and be able to add toolbar controls to apps at will.





