Word Counter using Bindings

Bindings in Cocoa are very powerful and can be used to organize, lay out, and display vast amounts of data in large apps, but bindings can also be used to make things more convenient in simple applications. To demonstrate this, let’s write a simple app which counts the number of words that is being typed by the user, and displays it in a text field that is updated dynamically through the magic of bindings!

First, we’ll make a simple Cocoa Application in Xcode. Open up the MainMenu XIB file and place a Label (NSTextField) and Text View (NSTextView) on the window, with the Label above the Text View.

You may want to position them so they look neat and tidy, since we’re all about aesthetics, but it’s not 100% necessary for the app to work. When you’re done setting up your window, it should look something like this:

Now there’s a few more things we need to do in Interface Builder before we’re done with this file. First, make sure that Rich Text is unchecked in the Text View’s Attributes (first) Inspector pane.

Now drag an Object from the Library and into your document, and name it AppController from the Identity (second-to-last) Inspector.

Making sure the Text View is now selected, go to the Bindings (fourth) Inspector and expand the Value binding. Choose “App Controller” from the list of what to bind to, and check “Bind to:”. Change Model Key Path to “string”. Make sure to check “Continuously Updates Value”.

What this doing is saying that we want to bind the Text View’s string value to App Controller’s “string” variable, and that whenever the Text View is edited or changed, to update App Controller’s “string” variable immediately.

Note: According to Apple’s documentation, when a binding is fired (for example, user types text in our Text View), the method -setValue:forKey: is called, which is going to raise an exception if the variable is not found (or an equivalent setter method), but if it does exist, it’s going to retain our object unless we explicitly tell it otherwise. This means that if you are not using Garbage Collection, you will need to release your ivar in the -dealloc method.

There’s one last thing we need to do in IB. Select your Label and choose the Bindings Inspector again. Bind it to the App Controller with “string” as the Model Key Path again, like we did with the Text View. If we wanted to simply count the characters, we could change the Model Key Path from “string” to “string.length” and leave it at that, but because we’re doing something somewhat tricky here, we’ll need to leave that as “string” and use a Value Transformer, which you should enter as “WordCountValueTransformer”.

The purpose of binding the value transformer is so that the raw value that is bound to (an NSString in this example) is not what we’re wanting to see in our Label, so we need to do some custom code to derive the value we want to see, which for this project is the number of words in the NSString.

So let’s create this App Controller class in Xcode now from the basic NSObject-derived template class file. After we’ve created it, we’ll need to create this “string” variable as an NSString for the bindings to work (otherwise your app will crash right when it’s launched). If you’re not using Garbage Collection, you’ll need to implement the -dealloc method which calls super’s implementation, and releases “string”.

One last thing we need to do before this app works, is create a Value Transformer named WordCountValueTransformer. You can consult the documentation on all the fun goodness of creating value transformers, but for now we’ll only take an in-depth look at one method, -transformedValue: which takes a “value” argument, in this case an NSString (our ’string’ instance variable). Don’t forget to set WordCountValueTransformer’s superclass to NSValueTransformer or your app will not work as expected (or at all)!

@implementation WordCountValueTransformer

+ (Class) transformedValueClass { return [NSNumber class]; }
+ (BOOL) allowsReverseTransformation { return NO; }

- (id) transformedValue:(id)value {
        int count = 0;
       
        if (value != nil) {
                NSTextStorage *textStorage = [[NSTextStorage alloc] initWithString:value];
                NSArray *words = [textStorage words];
                count = [words count];
                [textStorage release];
        }
       
        return [NSNumber numberWithInt:count];
}

@end

As you can see, we’re creating a very powerful object (NSTextStorage) which allows us to simply call the method “words” to get an array of each word in the Text View. This way, we let Cocoa do the hard work of parsing the string, and we just take the “count” of the words array and move on with our lives.

Now that you’ve completed your app, run it and see it in action. Every time you type into the text view, the label above it should be updated to reflect the word count (also taking punctuation into account). Sit back and enjoy it, as you’re now already half-way on your way to writing the next MS Word competitor!

Related posts:

  1. Understanding Object-Oriented Programming and the MVC System Introduction Before you begin to learn the basic syntax...

4 Responses

04.30.09

At the last screenshot, the number 15 is wrong. It should be 70…

04.30.09

Nevermind that last comment :P It counts words, not characters.

04.30.09

Awesome, bindings had eluded me, thanks.

04.30.09

Great tutorial! Thank you:-)

Leave Your Response

* Name, Email, Comment are Required