Cocoa Programming for Mac OS X - Aaron Hillegass [111]
NSURL *baseUrl = [NSURL URLWithString:
@"http://www.bignerdranch.com/"];
NSURL *url = [NSURL URLWithString:[c href]
relativeToURL:baseUrl];
[[NSWorkspace sharedWorkspace] openURL:url];
}
Build and run the application. Double-clicking on a title should open the page for the selected class in your default browser.
Challenge: Add a WebView
At the moment, you are using NSWorkspace to open the Web page in another application. Perhaps the user would like it if the the web page appeared in a sheet in the existing application (Figure 28.7).
Figure 28.7. Use WebView to Display Details
The challenge, then, is to add a new window with a WebView to your application. Bring the window onto the screen as a sheet.
You will need to add the WebKit framework to your project.
If you have a string representing a URL, you can get a WebView to load that URL by sending it the following message:
- (void)setMainFrameURL:(NSString *)URLString;
That should be all you need. If you want a progress indicator, you will need to make your controller the “frame load delegate” of the WebView:
[webView setFrameLoadDelegate:self];
Then your controller can implement the following methods:
- (void)webView:(WebView *)wv
didStartProvisionalLoadForFrame:(WebFrame *)wf;
- (void)webView:(WebView *)wv
didFinishLoadForFrame:(WebFrame *)wf;
- (void)webView:(WebView *)wv
didFailProvisionalLoadWithError:(NSError *)error
forFrame:(WebFrame *)wf;
Chapter 29. Blocks
Let’s pretend that we’re writing a zombie game. Specifically, we’re working on the zombie AI code. We want a method on our Zombie object to find nearby brains. So we start with this:
@implementation Zombie
- (NSArray *)brainsForFlags:(NSInteger)flags
{
return [[self game] allBrains];
}
@end
It’s a good start, but it would be a lot more useful it if returned the brains in order of proximity to the zombie, that is, sorted by the distance between the zombie and the brain. The zombie is hungry, after all. NSArray’s sortedArrayUsingSelector: is usually a great first choice for sorting. It calls the given selector on the objects in the array in order to compare them with their neighbors. For example, NSString provides a compare: method. Thus, we can use it to sort an array of strings:
NSArray *sortedStrings =
[theStrings sortedArrayUsingSelector:@selector(compare:)];
We might entertain adding a compareByDistanceToZombie: method to the Brain class. But how would it know which zombie it’s comparing the distance to? The method sortedArrayUsingSelector: doesn’t provide any way to pass contextual information to the sorting process.
NSArray’s sortedArrayUsingFunction:context: seems like a better choice. We can write a C function and tell sortedArrayUsingFunction:context: what function to use to compare the brains. Using it would look something like this:
NSInteger CmpBrainsByZombieDist(id a, id b, void *context)
{
Brain *brainA = a;
Brain *brainB = b;
Zombie *zombie = (__bridge Zombie *)context;
float distA = [zombie distanceToBrain:brainA];
float distB = [zombie distanceToBrain:brainB];
if (distA == distB) return NSOrderedSame;
else if (distA < distB) return NSOrderedAscending;
else return NSOrderedDescending;
}
- (NSArray *)brainsForFlags:(NSInteger)flags
{
NSArray *allBrains = [[self game] allBrains];
return [brains sortedArrayUsingFunction:CmpBrainsByZombieDist
context:(__bridge void *)self];
}
The void pointer context argument is used to provide additional data to the comparison function; we use this to pass the pointer to the Zombie instance (self). Note that the __bridge casting is necessary to convert an object reference into a type out of ARC’s control.
We’ve got a workable solution now. After some playtesting, however, we decide that we want our zombies to have a more varied palette. If frenzy mode is on, the zombies should seek out the brain with the highest IQ, no matter where it is in the game world. Now we need to supply multiple parameters to CmpBrainsByZombieDist, but it all needs