Chapter 21. NSTimer

An instance of NSButton has a target and a selector (the action). When the button is clicked, the action message is sent to the target. Timers work in a similar way. A timer is an object that has a target, a selector, and a delay, which is given in seconds (Figure 21.1). After the delay, the selector message is sent to the target. The timer sends itself as an argument to the message. The timer can also be set to send the message repeatedly.

NSTimer

Figure 21.1. NSTimer

To play with timers a bit, you will create a typing tutor application. The application will have two BigLetterView objects. One will display what the user should type, and the other will display what the user has typed (Figure 21.2). An NSProgressIndicator will display how much time is left. When 2 seconds has passed, the application will beep to indicate that the user took too long. Then the user is given 2 more seconds.

Completed Application

Figure 21.2. Completed Application

You will create an AppController class. When the user clicks the Go button, an instance of NSTimer will be created. The timer will send a message every 0.2 second. The method triggered will check whether the two views match. If so, the user is given a new letter to type. Otherwise, the progress indicator is incremented. If the user pauses the application, the timer is invalidated.

Figure 21.3 shows the object diagram.

Object Diagram

Figure 21.3. Object Diagram

Lay Out the Interface

Go back to your TypingTutor project. Open the MainMenu.nib.

Create a new subclass of NSObject called AppController. Add three outlets to AppController: inLetterView, outLetterView, and progressView. inLetterView and outLetterView will refer to BigLetterView objects. progressView will refer to an NS ProgressIndicator. Add one action: stopGo: (Figure 21.4).

AppController's Actions and Outlets

Figure 21.4. AppController's Actions and Outlets

Create the files for AppController. Create an instance of AppController.

Select the BigLetterView on the left. From the Layout menu, choose the Make subviews -> Box menu item. Relabel the box Type here. Group the other BigLetterView in a box, and relabel that box Match this.

Drop an NSProgressIndicator on the window. Use the inspector to make it not indeterminate. Set its range to be 0 to 100 (Figure 21.5).

Inspect the Progress Indicator

Figure 21.5. Inspect the Progress Indicator

Put a button on the window. Using the inspector, set its title to Go and its alternate title to Pause. Make the button type Rounded Bevel Button, and set its behavior to Toggle. Also, set it to have no icon (Figure 21.6).

Inspect the Button

Figure 21.6. Inspect the Button

Make Connections

Control-drag from the button to the AppController object. Set the action to be stopGo: (Figure 21.7).

Connect the Button to the AppController

Figure 21.7. Connect the Button to the AppController

Control-drag from the AppController to the NSProgressIndicator, and set the progressView outlet.

Control-drag from the AppController to the BigLetterView on the left, and set the inLetterView outlet (Figure 21.8).

Connect the inLetterView Outlet

Figure 21.8. Connect the inLetterView Outlet

Control-drag from the AppController to the BigLetterView on the right, and set the outLetterView outlet (Figure 21.9).

Connect the outLetterView Outlet

Figure 21.9. Connect the outLetterView Outlet

Adding Code to AppController

Add the following instance variables and methods to AppController.h:

#import <Cocoa/Cocoa.h>
@class BigLetterView;

@interface AppController : NSObject
{
    IBOutlet BigLetterView *inLetterView;
    IBOutlet BigLetterView *outLetterView;
    IBOutlet NSProgressIndicator *progressView;
    int count;         // How many times has the timer gone off?
    NSTimer *timer;
    NSArray *letters;  // The array of letters that the user will type
    int lastIndex;     // The index in the array of the
                       // letter the user is trying to type
}
- (void)showAnotherLetter;
- (IBAction)stopGo:(id)sender;
@end

Implement the following methods in AppController.m:

#import "AppController.h"
#import "BigLetterView.h"

// Number of times the timer will fire
#define TICKS 10

@implementation AppController

- (id)init
{
    if (self = [super init]) {

      // Create an array of letters
      letters = [[NSArray alloc] initWithObjects:@"a", @"s",
                     @"d",@"f", @"j", @"k", @"l", @";",nil];
      lastIndex = 0;
      // Seed the random number generator
      srandom(time(NULL));
    }
    return self;
}

- (void)awakeFromNib
{
    [self showAnotherLetter];
}
- (void)showAnotherLetter
{
    int x;
    //Choose random numbers until you get a different
    // number than last time
    x = lastIndex;
    while (x == lastIndex){
        x = random() % [letters count];
    }
    lastIndex = x;
    [outLetterView setString:[letters objectAtIndex:x]];

    [progressView setDoubleValue:0.0];
    count = 0;
}

- (IBAction)stopGo:(id)sender
{
    if ([sender state] == 1) {
        NSLog(@"Starting");

        // Create a timer
        timer = [[NSTimer scheduledTimerWithTimeInterval:0.2
                                         target:self
                                       selector:@selector(checkThem:)
                                       userInfo:nil 
                                        repeats:YES] retain];
    } else {
        NSLog(@"Stopping");

        // Invalidate and release the timer
        [timer invalidate];
        [timer release];
    }
}

- (void)checkThem:(NSTimer *)aTimer
{
    if ([[inLetterView string] isEqual:[outLetterView string]]) {
        [self showAnotherLetter];
    }
    count++;
    if (count > TICKS){
        NSBeep();
        count = 0;
    }
    [progressView setDoubleValue:(100.0 * count) / TICKS];
}
@end

Build and run your application.

Note, once again, that we have separated our classes into views (BigLetterView) and controllers (AppController). If I were creating a full-featured application, I would probably also create model classes like Lesson and Student.

For the More Curious: NSRunLoop

NSRunLoop is an object that waits. It waits for events to arrive and then forwards them to NSApplication. It waits for timer events to arrive and then forwards them to NSTimer. You can even attach a network socket to the run loop, and it will wait for data to arrive on that socket.

Most Cocoa applications have only one thread. This is possible because the one thread is controlled by the run loop. Many people who think they need to create a multithreaded application later realize that their application would have been more efficient and easier to write if they had used the run loop more wisely. It is rarely necessary to create multithreaded applications.

Challenge

Change your ImageFun application so that autoscrolling is timer driven. Delete your mouseDragged: method from StretchView. In mouseDown:, create a repeating timer that invokes a method in the view every tenth of a second. In the invoked method, autoscroll using the current event. To get the current event, use NSApplication's currentEvent method:

NSEvent *e = [NSApp currentEvent];

(Remember that NSApp is a global variable that points to the instance of NSApplication.) Invalidate and release the timer in mouseUp:. Note that the autoscrolling becomes much smoother and more predictable.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset