Big Nerd Ranch
Hopefully, I will continue to improve the content of this site in the months to come. To that end—among others—I’m at the Big Nerd Ranch in Atlanta, Georgia for their Advanced Mac OS X Programming course this week. I’m very excited and hope to learn as much as I can possibly fit into my brain.
Using AppleScript to Automate an iChat Video Chat
I had to write a script to automatically start iChat at login and start a video chat with a specified screenname. I wanted to only start the chat if the user was online and available, and quit iChat on an error or if the chat ended. So here’s the script I have:
using terms from application "iChat" tell application "iChat" activate log in delay 5 set theBuddy to buddy "ScreennameGoesHere" of service "AIM" try set theStatus to status of theBuddy on error errmesg number errno set message to display dialog "The user is currently unavailable." buttons {"OK"} quit return end try if theStatus is available then set theCapabilities to get capabilities of theBuddy if (theCapabilities contains multiperson video) then send "A user is attempting to contact you." to theBuddy delay 2 tell service "AIM" to make video chat with properties {participants:theBuddy} set theChat to result delay 30 try set theStatus to the av connection status of theChat on error errmesg number errno quit return end try repeat while theStatus is not ended delay 5 try set theStatus to the av connection status of theChat on error errmesg number errno quit return end try end repeat quit return else set message to display dialog "The user cannot video chat at this time. Please try again later." buttons {"OK"} quit return end if else set message to display dialog "The user is currently unavailable." buttons {"OK"} quit return end if end tell end using terms from
A couple of gotchas:
- I tried using
video chatinstead ofmultiperson video, but that always returned false. I don’t know why. - Once the video chat has ended, you can’t poll its status (hence the
tryblock).
New iPhone Project: uWarranty
So, I created a new iPhone application called uWarranty. It used an unpublished Apple API for warranty status (from selfsolve.apple.com), and so I got this after submitting it:
Thank you for submitting your application to the App Store. Unfortunately, your application, uWarranty, cannot be added to the App Store because it violates section 3.3.7 of the iPhone Developer Program License Agreement:
“Applications may not perform any functions or link to any content or use any robot, spider, site search or other retrieval application or device to scrape, retrieve or index services provided by Apple or its licensors, or to collect, disseminate or use information about users for any unauthorized purpose.”
OK, I get it. That’s fine and is Apple’s prerogative. But now I have this program and all the development time that went into it. So what do I do? I guess the answer is to open-source it, just like AppSales Mobile. Watch this space for more details as I clean up the code and throw it up on a public repository somewhere.
Cocoa Touch Tutorial: Stripping Non-Alphanumeric Characters on Entry in a UITextField
In a previous post, I showed you how to trim non-alphanumeric characters from a string. Here I’ll go more in-depth and show a method that I wrote to restrict text entry in a UITextField to alphanumeric characters. Since I also wanted the characters to be uppercase, I’ll also ensure that only uppercase characters are allowed.
This should all happen in the - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string method of your UITextField’s delegate (which, of course, must implement the UITextFieldDelegate protocol). I’ve implemented it as follows:
- ( BOOL )textField:( UITextField * )textField
shouldChangeCharactersInRange:( NSRange )range
replacementString:( NSString * )string
{
/*
* We only want uppercase letters and numbers in this text field, so if
* this method is adding something else, we don't want it. But we also
* want to support copy-and-paste, so it's not always going to be one
* character added.
*/
BOOL shouldAllowChange = YES;
The shouldAllowChange variable is set to YES initially because we want to allow this change when possible. The method will test the string to see if it meets criteria for rejection as we move forward.
NSMutableString *newReplacement =
[[ NSMutableString alloc ] initWithString:[ string uppercaseString ]];
if ( ! [ string isEqualToString:newReplacement ]) {
shouldAllowChange = NO;
}
First, we define newReplacement. It’s an NSMutableString so that if we discover non-alphanumeric characters in it, we can remove them on-the-fly. It also serves as a convenient string against which we can test to see if string is already uppercase.
NSCharacterSet *desiredCharacters =
[ NSCharacterSet alphanumericCharacterSet ];
for ( NSUInteger i = 0; i < [ newReplacement length ]; i++ ) {
unichar currentCharacter = [ newReplacement characterAtIndex:i ];
if ( ! [ desiredCharacters characterIsMember:currentCharacter ]) {
shouldAllowChange = NO;
[ newReplacement deleteCharactersInRange:NSMakeRange( i, 1 )];
i--;
}
}
In this section, we define the NSCharacterSet that we want to work with - in this case, the alphanumeric character set. We go through one character by a time and if the current character isn’t alphanumeric, we remove it from the NSMutableString (decrementing i so that we don’t inadvertently skip a character) and set our shouldAllowChange flag accordingly.
if ( shouldAllowChange ) {
[ newReplacement release ];
return YES;
} else {
[ textField setText:[[ textField text ]
stringByReplacingCharactersInRange:range
withString:newReplacement ]];
[ newReplacement release ];
return NO;
}
}
To finish, if shouldAllowChange is still true, we return YES and allow the replacement characters to be added. Otherwise, we return NO, but not before using our replacement replacement string (say that ten times fast) to manually edit the text field’s text. The end result is a text field that will consist only of uppercase letters and numbers.
User Accounts Disabled
To help fight spam, I’ve disabled user account registration on this blog and deleted all subscribers. I apologize if anyone actually used that feature; if RSS isn’t enough for you, e-mail me and I’ll make you an account. Unless you try to sell me Viagra.
Using Apple’s SimplePing on iPhone OS
If you try out of the box to compile Apple’s “SimplePing” code sample on the iPhone OS, you’ll wind up with a lot of errors as some files don’t exist in those SDKs. Specifically, you need these files (you need more than just these files to compile, obviously, but these are the ones that aren’t included):
/usr/include/netinet/ip.h/usr/include/netinet/in_systm.h/usr/include/netinet/ip_icmp.h/usr/include/netinet/ip_var.h
So here’s a quick Bash script that links the relevant files to your iPhone OS and iPhone Simulator SDKs:
for path in /Developer/Platforms/iPhone*/Developer/SDKs/*; do
for file in /usr/include/netinet/ip.h \
/usr/include/netinet/in_systm.h \
/usr/include/netinet/ip_icmp.h \
/usr/include/netinet/ip_var.h; do
if [ ! -f "${path}${file}" ]; then
sudo ln "${file}" "${path}${file}"
fi;
done;
done
I’ve spoken to an Apple engineer and confirmed that this is the best way to do it, as well as filed a bug, which I encourage you to do as well if this annoys you.
Cocoa Tutorial: Strip Non-Alphanumeric Characters from an NSString
Let’s say you have an NSString that contains both alphanumeric and non-alphanumeric characters and you want to strip the non-alphanumeric characters out of it. The hard way is to manually go through, character-by-character, and put the character in a new string if it matches certain criteria. But why do it the hard way?
Apple provides a class that we can use for this to great effect: NSCharacterSet. We want alphanumeric characters, so we can create a character set of the characters we want using this method:
NSCharacterSet *alphanumericSet = [ NSCharacterSet alphanumericCharacterSet ];
Now we have a character set like we want. We just need a way to turn our string into a string that contains only those characters. Unfortunately, the closest thing in NSString’s implementation is the -stringByTrimmingCharactersInSet: method. But that seems to do the opposite of what we want. Fortunately NSCharacterSet has our back here. We can use the -invertedSet method. So here is our final code:
NSString *beginningString = @"Some string with non-alphanumeric characters. !@#$%^&*()";
NSCharacterSet *nonalphanumericSet = [[ NSCharacterSet alphanumericCharacterSet ] invertedSet ];
NSString *endingString = [ beginningString stringByTrimmingCharactersInSet:nonalphanumericSet ];
In this example, endingString will be equal to “Somestringwithnonalphanumericcharacters”.
UPDATE: As it turns out, this only works if the non-alphanumeric characters are at the beginning or end of the NSString. Whoops.
Automatically get the latest Chromium snapshot with launchd
I’ve been checking out the snapshots of Chromium recently, and they’re coming quicker than you can say “multithreaded web browser.” To facilitate always having the latest version, I wrote a quick LaunchAgent that takes care of it on Mac OS X. First, I have a script named ~/bin/chromiupdate:
#!/bin/bash
# Downloads the latest version of Chromium.
remove_working_dir()
{
rm -rf "${WORKING_DIR}"
exit 0
}
USER_DIR=$(dscl . -read /Users/$(whoami) NFSHomeDirectory | awk '{ print $2 }')
USER_APP_DIR="${USER_DIR}/Applications"
CHROMIUM_DIR="${USER_APP_DIR}/Chromium.app"
LATEST_URL="http://build.chromium.org/buildbot/snapshots/sub-rel-mac/LATEST"
TMP_DIR="/private/tmp"
WORKING_DIR="${TMP_DIR}/.chromium_launchd"
URL_BEGIN="http://build.chromium.org/buildbot/snapshots/sub-rel-mac"
if [ ! -d "${CHROMIUM_DIR}" ]; then
mkdir -p "${CHROMIUM_DIR}"
fi
INSTALLED_VERSION="$(defaults read "${CHROMIUM_DIR}/Contents/Info" SVNRevision)"
VERSION=$(curl "${LATEST_URL}")
if [ "${VERSION}" != "${INSTALLED_VERSION}" ]; then
logger Installed Chromium version \(${INSTALLED_VERSION}\) does not equal \
latest version \(${VERSION}\), updating now...
mkdir "${WORKING_DIR}" || exit 1
trap remove_working_dir 1 2 3 6 15
cd "${WORKING_DIR}" || exit 1
curl -O "${URL_BEGIN}/${VERSION}/chrome-mac.zip"
unzip chrome-mac.zip
rsync -HavP --exclude="Contents/MacOS/chrome_debug.log" \
"${WORKING_DIR}/chrome-mac/Chromium.app/" "${CHROMIUM_DIR}/"
if [ "$(ps -aef | grep -i chromium | grep -v grep)" != "" ]; then
open "${USER_DIR}/Library/Scripts/Chromium Update Dialog.app"
fi
logger "Chromium update complete. Version ${VERSION} installed."
remove_working_dir
else
logger Installed Chromium version \(${INSTALLED_VERSION}\) is up-to-date. \
No action needed.
fi
exit 0
Next, I have a property list named ~/Library/LaunchAgents/com.slaunchaman.chromium.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN http://www.apple.com/DTDs/PropertyList-1.0.dtd >
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.slaunchaman.chromium</string>
<key>Program</key>
<string>/Users/slauncha/bin/chromiupdate</string>
<key>KeepAlive</key>
<false/>
<key>StartInterval</key>
<integer>3600</integer>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>/dev/null</string>
<key>StandardErrorPath</key>
<string>/dev/null</string>
</dict>
</plist>
Finally, I have an AppleScript at ~/Library/Scripts/Chromium Update Dialog.app:
display dialog "Chromium was just updated. You should restart it."
The LaunchAgent runs once an hour, checking to see if the installed version of Chromium is older than the latest snapshot. If so, it downloads it and uses rsync to copy the changes. The script places Chromium in ~/Applications, but it shouldn’t be hard to modify to put it into /Applications.
Take Me Home 1.1.1 Released
Take Me Home version number 1.1.1 is now available via iTunes. This release has the following features:
-Tweaked UI
-Progress bar now updates more accurately
-Current location accuracy now displayed to user
Take Me Home version 1.1.1 is a free update to all Take Me Home owners.
Cocoa Touch Tutorial: Extract Address Book Address Values on iPhone OS
This is the first of what I hope to be several Cocoa Touch tutorials on this site. I was doing some furious Googling last night trying to find out how to get a contact’s street address from the Address Book for an upcoming update to Take Me Home, and I realized that it’s complicated and there aren’t any good tutorials online. So, after I figured it out, I commented it up so that hopefully, if you’re reading this, you’ll save some time that I didn’t.
Before you read this tutorial, you should go through Apple’s excellent Address Book Programming Guide for iPhone OS. This tutorial will rely on the QuickStart application you write in the guide, so do that first.
The first thing we need to do is add an address field to the QuickStart application. Use Interface Builder to add a new UILabel underneath the two you already have. You may want to stretch it to fill the entire width of the screen, like so:

Add a new UILabel underneath the exisiting two.
Now, add the information about this label to QuickStartViewController.h:
// // QuickStartViewController.h // QuickStart // #import <UIKit/UIKit.h> #import <AddressBook/AddressBook.h> #import <AddressBookUI/AddressBookUI.h> @interface QuickStartViewController : UIViewController <ABPeoplePickerNavigationControllerDelegate> { IBOutlet UILabel *firstName; IBOutlet UILabel *lastName; IBOutlet UILabel *addressLabel; } @property (nonatomic, retain) UILabel *firstName; @property (nonatomic, retain) UILabel *lastName; @property (nonatomic, retain) UILabel *addressLabel; - (IBAction)showPicker:(id)sender; @end
Be sure to go back into Interface Builder and connect File’s Owner in QuickStartViewController.xib to addressLabel.
Now, we have to change the method that gets called when you click on a person in the ABPeoplePicker. As it is at the end of the QuickStart tutorial, once you select a person the picker is dismissed. So, we do the following in QuickStartViewController.m:
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person { NSString *name = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty); self.firstName.text = name; [name release]; name = (NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty); self.lastName.text = name; [name release];[self dissmissModalViewControllerAnimated:YES];return YES; }
Note that you have to delete the line that dismisses the modal view controller; if you don’t, the people picker is dismissed before you have a chance to get the address. When you delete it, the people picker will continue when you select a person. Next up, we have to write the method for when someone selects an address on the next screen. Here’s the method:
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier { // Only inspect the value if it's an address. if (property == kABPersonAddressProperty) { /* * Set up an ABMultiValue to hold the address values; copy from address * book record. */ ABMultiValueRef multi = ABRecordCopyValue(person, property); // Set up an NSArray and copy the values in. NSArray *theArray = [(id)ABMultiValueCopyArrayOfAllValues(multi) autorelease]; // Figure out which values we want and store the index. const NSUInteger theIndex = ABMultiValueGetIndexForIdentifier(multi, identifier); // Set up an NSDictionary to hold the contents of the array. NSDictionary *theDict = [theArray objectAtIndex:theIndex]; // Set up NSStrings to hold keys and values. First, how many are there? const NSUInteger theCount = [theDict count]; NSString *keys[theCount]; NSString *values[theCount]; // Get the keys and values from the CFDictionary. Note that because // we're using the "GetKeysAndValues" function, you don't need to // release keys or values. It's the "Get Rule" and only applies to // CoreFoundation objects. [theDict getObjects:values andKeys:keys]; // Set the address label's text. NSString *address; address = [NSString stringWithFormat:@"%@, %@, %@, %@ %@", [theDict objectForKey:(NSString *)kABPersonAddressStreetKey], [theDict objectForKey:(NSString *)kABPersonAddressCityKey], [theDict objectForKey:(NSString *)kABPersonAddressStateKey], [theDict objectForKey:(NSString *)kABPersonAddressZIPKey], [theDict objectForKey:(NSString *)kABPersonAddressCountryKey]]; self.addressLabel.text = address; // Return to the main view controller. [ self dismissModalViewControllerAnimated:YES ]; return NO; } // If they didn't pick an address, return YES here to keep going. return YES; }
Let’s go through that in more detail. The method gives us the following information: an ABRecordRef of the person we’ve selected, an ABPropertyID of the property slected (in this case, we ensure that it’s the address) and an ABMultiValueIdentifier of which address we’ve selected. It is important to note that the ABPropertyID is equal to kABPersonAddressProperty when you select any address; that is, there is only one address property. This one address property holds the values in an ABMultiValue, each at a specific index. Here are the steps we take in the code:
- The first thing we do is define our ABMultiValue, multi, and copy the contents of the selected value into it.
- Then we define an NSArray, theArray, into which to copy the multiple values. But which one do we want?
- Each address has an identifier, which the method gives to us as identifier, but we reference them by index when getting them out of the array. So, we need to create an index (which we’ll store as an unsigned integer), theIndex, and set it to the return value of the
ABMultiValueGetIndexForIdentifierfunction. Now that we have the index, we know which value of the array to store . They’re stored as type CFDictionary, which have key-value pairs for us to use, so we define an NSDictionary, theDict to put them into. - First, we need to know how many key-value pairs there are, so we use the
countmethod and store the return value in an unsigned integer, theCount. Be sure that this variable doesn&rquo;t change—you don’t want to assume that there are more members in the array than there actually are, as that can lead to nasty memory problems. For that reason I’ve defined it as a constant. - Now, we define two NSString arrays, keys[theCount] and values[theCount], and then we’re ready for action.
- Next we use the NSDictionary
getObjects: andKeys:function to copy the keys and values. The function copies the data, and we can construct our street address. For the purpose of this example, I’m going to make the address a single line, but you do with it what you want. - Finally, we create a final NSString to put the formatted address into, pull the values out of the dictionary into the appropriate place, and we’re all done!
Update 2010-07-27: Removed [ theDict release ];, some bad memory management.

