Archive

Archive for the ‘MacOSX’ Category

Upgrading to Xcode 4.3

February 18th, 2012 5 comments

Xcode 4.3 was released on February 16th. It is now truly a Mac App Store application.

With Xcode 4.2, you downloaded an “Install Xcode” application from the Mac App Store, which in turn “installed” Xcode in the usual location, “/Developer”, which is a folder named “Developer” at the root of your hard drive.

Furthermore, if you wanted Xcode to automatically update (from the Mac App Store), you had to keep the “Install Xcode” application (1.8 GB) in your Applications folder, as that was what the the Mac App Store actually updated; you then had to run “Install Xcode” manually to update.

This has changed for the better.

Xcode.app now installs directly in the Applications folder. In fact, it will helpfully propose to move the older, obsolete installation to the Trash for you:

Xcode move to Trash

Xcode is also smaller, since a lot of (probably) lesser-used software has moved to optional downloads. Peter Hosey has a comprehensive list.

In my case, I had one more thing to do: install the command-line tools. They can be found in Xcode > Preferences > Downloads > Components. To get them working properly, here is what I had to do:

  1. sudo xcode-select -switch /Applications/Xcode.app
    This updates the command-line Xcode tools to point to the new location, and is only important if you are migrating from 4.2. A new install should work fine.
  2. Install autoconf. Some of my projects (and many unix-based ones) use autoconf to generate ‘configure’ files, and this tool is missing from the new install. It was present in earlier installs, so I have filed a radar.
    In the meantime, I was able to download the latest version (2.84 at the time of this writing) and install it with the usual ./configure; make; sudo make install dance
  3. Update any scripts that have a hard-coded ‘/Developer’ path.
    Since everything is in the ‘/Applications/Xcode.app’ bundle, the new path is ‘/Applications/Xcode.app/Contents/Developer’. If you don’t want to hard-code it (for instance, if you use Xcode 4.3 but your colleagues are still on Xcode 4.2), you can use the output of xcode-select --print-path to find the path to ‘Developer’ on any system. This works in Xcode 3 as well.

Xcode 4.3 requires MacOSX Lion 10.7.3.

Categories: iOS, MacOSX, Xcode Tags:

iPad or Netbook?

September 6th, 2010 4 comments

[This is not as developer-oriented as my other posts, but many people asked me to share this story so here goes… —Philippe]

The Setup

We have three children in the house: twin teens and one pre-teen. We also have three usable (read: Intel-based) computers. I have an original MacBook Pro, my wife has a Mini and the kids share another (recent) Mac Mini.

Three kids on a computer was becoming a constant source of tension among them, which I wanted to alleviate. Their Mini is the most powerful of all our machines with a fairly decent video card, a large display and a Wacom Intuos 3 they use all the time in Painter and Acorn.

The Problem

The twins want their own “computer”. They want to be able to surf the web, type reports for class, chat with their friends and watch videos. And go on Facebook.

If money was no object, I would buy them each a MacBook. They know how to use it, won’t get viruses, won’t always come to me for questions, and all our current apps (Pages, iTunes, Firefox, Adium) will work just fine. Plus they would be able to sync their iPods before going to school without it being a dramatic production 5 minutes before they leave, because they must have the latest video on there for the bus ride.

But of course, money is an issue. Plus, at this rate, they won’t make it through University with the same MacBook because by that time, the MacBook will be 7 years old. So I was looking at buying 4 MacBooks over the next 3-4 years, which is less than ideal.

Enter the iPad

Looking at their usage patterns, I figured that an iPad for each teen would cover 70-80% of their computing needs (maybe with a stand and an external keyboard, too). It seemed like an ideal solution.

Buy each teen an iPad and they can:

  • Surf the web, chat, write papers, watch (some) videos
  • Have a small, long-lasting-battery device they can take anywhere
  • Sync occasionally to the main computer, as needed

In return I get:

  • to buy two iPads for the price of one MacBook (a 50% saving!)
  • a lot less nagging until College
  • almost no maintenance
  • hopefully a lot less conflicts around the family computer
  • peace of mind (no webcam is a plus!)

So I bought one iPad and lent it to each child for her exclusive use, for a week. They would tell me if my assumptions were correct.

iPad Results

Being the new toy, the iPad got quite a bit of play at our house. Since it is small and portable, it can be used anywhere. I like it for books and rss feeds, my wife watches CrunchyRoll, YouTube and Facebook are perennial hits.

I also took the iPad for a week at WWDC as my note-taking, twittering machine and that was a smash hit, thanks to SimpleNote and Notational Velocity.

Overall, we all like the iPad. We really wished it had separate user accounts, or at least a “guest” mode so the one person who’s responsible for it doesn’t have to expose his or her mail, Twitter, Safari bookmarks, contacts, etc… to everyone else who wants to use the one iPad.

No Flash

The biggest problem with the iPad was the lack of Flash. I personally don’t care for Flash and its battery-sucking, lap-melting performance, but my daughters watch a lot of Japanese cartoons and play Flash games on Facebook, and those will take some time to transfer to the new world of iOS apps, HTML5 and H264.

A secondary concern was the iPad’s unitasking. I view it as a feature but they like to have on their screen an IM client, a YouTube video and some web browser / text editor / paint program. You can’t easily do that simultaneously on the iPad

Netbooks to the rescue!

I have an original OLPC and when I (quickly) got bored with it, my plan was to recycle it to my children as a small laptop. That experiment is mostly a failure du to the laptop’s anemic performance and inability to hold a decent charge. Seriously, its browser is slower than the Wii and forget about Flash!. However, it has been a good way to teach Python to my youngest daughter, and the laptop’s whimsical, kid friendly design is a hit wherever she takes it.

So you can understand my reluctance about netbooks. It was out of the question that I would buy my daughters a 600$ laptop: those are heavy, ugly monstrosities; try to get anything decent and you soon get within striking distance of a MacBook so why not get a MacBook?

We started talking about netbooks. My kids understood they were slower than the Mac Mini, and had a small screen and keyboard but those were not deal-breakers by any means. In fact, they prefer the small form factor because (duh!) it takes less space and is more “personal”, a feature they really appreciated in the iPad. The intimacy of this device is a feature all by itself.

Just as for the iPad, I decided to buy a small netbook as an experiment. My conditions were simple:

  • it cannot cost more than 400$
  • colors don’t matter. You’ll get to put stickers on it if we keep it (because in three years, when it’s really time for a MacBook, that thing will be old and no one will care about it)
  • it cannot run Windows. I do not want that administration task.

I found an HP Mini 110, which had the additional advantage of being a good candidate for a hackintosh, should I end up with this system. And the price was right at 280$.

Hardware-wise, the bilingual keyboard is surprisingly good. The trackpad is a little too sensitive and its buttons a bit mushy. I will probably buy an external, corded mouse in the next few days.

There are three USB ports, one SD card slot (perfect for their digital cameras) and one VGA-out port.

It comes with Windows 7 Starter, which is an restricted version of Windows 7. When I told my daughter that she could not even change the desktop wallpaper, she was appalled and took it as the best reason not to run Windows on this system…

As I was downloading Ubuntu Netbook (see below), she took the time to go through the first-run experience of Windows 7 because she knew it did not matter, we were going to erase it anyways. As a typical user, she was almost forced to install a ton of crapware programs and trial editions (Norton Antivirus? Really?). There was even a HP-branded user interface that installed itself in the bootloader! I kid you not! Getting rid of this abomination to be able to access the BIOS and boot from a USB key was not trivial.

Enter Ubuntu

Ubuntu Linux is the version I use at work. They have a netbook edition which is optimized for small screens and slower processors, so that is what I downloaded.

There’s even a manga comic, so that’s a big hit in this house!

You install the .iso on a USB stick (which is easiest to do under Windows than Mac, mind you) and from there, select the USB stick in your boot menu. I had to press Esc and then F9 on my HP Mini to get the menu, instructions which were on the computer’s screen. From there, follow the prompts and either run from the USB stick or install to the hard drive.

Ubuntu is available in many languages (from the same .iso), we chose to install French. It even recognized the French keyboard on the netbook, which was a concern of mine.

The overall look of Ubuntu is very good. Easily-readable text, good French translations and some obvious attention to details like rounded corners and fade-in, fade-out animations à la Growl.

Though it did download and install updates for over an hour, once we installed the Flash Player plugin all the sites that were previously inaccessible on the iPad became usable.

I was expecting everything to work, and everything did: the keyboard, trackpad, battery indicator, volume control, backlight control. It even goes to sleep when you close the lid!

I have not gotten the built-in speakers to work, but earphones work fine.

I only had one scare. My initial install failed to set up the wireless card. We knew it worked because Windows had detected it when we first booted, and I spent a few hours googling a solution and getting n00b-level advice on IRC.

The solution was simple: when installing, plug an ethernet cable into the netbook. As part of the install process, Ubuntu downloads additional files as needed. With this simple action, everything worked great.

Performance is very good. It appears subjectively faster than Windows 7 Starter on the same machine. I was worried about Flash performance but it is acceptable, definitely working for 360-480p video (the screen is 1024 x 600). Facebook games are fine.

Applications

We’re still evaluating this, but even though the big application is Firefox, we have found suitable replacements for Mail, IM and even homework. There are apps to manage your iPod which work fine with their Nanos.

They miss the “big” apps like Painter and Acorn (and to a lesser degree, Pages and iMovie) but that’s what the main family computer is for.

I have not worked out a backup strategy yet. I have a Time Capsule for the Macs in the house, which works with Time Machine. For their netbooks, I think a simple, cron-based hourly rsync of their home folder to a folder on on the family computer could do the trick.

Conclusions

They say that Linux is free only if your time is worth nothing, and that has some merit. But once it is running, it is very easy to use.

Ubuntu in particular is very discoverable. The netbook-specific version shows an attention to details like the small screen and the important applications, and that made all the difference. Even the application installer is easy to use. And it’s all free! Which amazes my daughters because I make my living writing software for money.

The Ubuntu Software Center in particular makes it trivial to discover, install and uninstall software. When she was not totally happy with the built-in Empathy‘s support for MSN Chat, my daughter found and installed amsn by herself. That is ease of use!

Aside from my one scare with the wireless card not working, the experience was very positive. In fact, I just bought a second, identical netbook (10$ less this time) and the install was seamless. We did not even boot into Windows.

The best part is I bought two netbooks for the price of one iPad. Another 50% savings! Even more if you account for the inevitable iPad apps like Pages and Keynote, and the external keyboard and dock. All of these easily add 100$ to the price of any iPad.

I always hope that my daughters will become proficient with computers, not because they know to “click there” to go to the Internet but because they have a higher-level understanding of how Operating Systems operate, regardless of MacOSX, Windows, Linux or more.

Because we have no idea what the computers from the future look like. And I want them to be ready.

Categories: MacOSX Tags:

Introducing PhFacebook, a Cocoa framework to Facebook’s API

August 29th, 2010 12 comments

For the upcoming version of iChibi, I needed a MacOSX interface to Facebook’s API.

There are several iOS libraries (FB Connect, ShareKit…) but I found only one that was MacOSX-specific: MBAbeFook, which implements the (deprecated) Facebook REST API.

This was a problem because:

  • I need extended permissions, and it’s a song-and-dance to get those using the REST API
  • The authentication UI from REST may present checkboxes to the user for extended permissions.
    Users don’t check boxes. They click “I agree”…
  • The REST API is deprecated.

So I wrote my own. PhFacebook is an embeddable MacOSX framework to easily access Facebook’s API.

  • Uses Facebook’s new ‘graph’ API internally, handles OAuth in a WebView for you and returns JSON strings.
  • Comes with a sample application to show you how to use it.
  • Supports extended permissions.
  • Localized in English and French.

Find it on github. MIT-licensed.

Categories: Development, MacOSX Tags:

Tap-enabled UITableView section headers (and footers)

January 31st, 2010 Comments off

Displaying ads

For a project I’m working on, I had the need to display inline ads within a UITableView of news.

My initial thought was simply to create a different kind of cell every 10 cells, and that cell knows how to display itself:

- (UITableViewCell*) tableView: (UITableView*) atableView 
  cellForRowAtIndexPath: (NSIndexPath*) indexPath 
{
  static NSString *NewsCellIdentifier = @"NewsCell";
  static NSString *AdCellIdentifier = @"AdCell";
  
  UITableViewCell *cell = nil;
    
  // Set up the cell...
  if (0 == indexPath.row % 10)
  {
    CMAdImageTableViewCell *adCell = 
      (CMAdImageTableViewCell*)[tableView dequeueReusableCellWithIdentifier: AdCellIdentifier];
    if (adCell == nil) 
    {
      adCell = [[[CMAdImageTableViewCell alloc] 
        initWithStyle: UITableViewCellStyleSubtitle 
        reuseIdentifier:AdCellIdentifier] autorelease];
    }
    /* more cell customization... */
    cell = adCell;
  }
  else
  {
    CMNewsTableViewCell *newsCell = 
      (CMNewsTableViewCell*)[tableView dequeueReusableCellWithIdentifier: NewsCellIdentifier];
    if (newsCell == nil) 
    {
      newsCell = [[[CMNewsTableViewCell alloc] 
        initWithStyle: UITableViewCellStyleSubtitle 
        reuseIdentifier: NewsCellIdentifier] autorelease];
    }
    /* more cell customization... */
    cell = newsCell;
  }
  
  [cell setNeedsDisplay];
  
  return cell;
}

This works. There is a CMAdImageTableViewCell created every 10 cells. And since the UITableView delegate can get the -(void) tableView: didDeselectRowAtIndexPath: message, you have a chance to intercept a tap on the ad cell and send the user to the ad’s website.

However, you have to do some bookkeeping. For instance, the total number of cells will be the sum of all the news cells and your ad cells, or [news count] + [news count] % 10. You also have to find the proper offset in your model when you get an indexPath, etc. Not difficult, but annoying.

Another issue is that TableView cells all look and behave the same. An Ad cell doesn’t say “I’m an ad”, it just says “I’m another cell”, by the way it scrolls. Your ad design will probably be different than your news design to tell them apart, but it would be nice to have another, subtle way to differentiate the ads.

Section Headers

UITableViews can be separated in sections, with an optional header and footer. These sections stay on screen, at the top and/or bottom, and are overlaid on top of the UITableView’s actual data. They can be semi-transparent, allowing to to see the table’s data underneath. You can see sections in action in many apps, including the Contacts application on your iPhone: notice the section headers “A”, “B”, “C”.. as you scroll down?

To create sections, just return the number of sections in your delegate’s -(NSInteger) tableView: numberOfRowsInSection:. Once you specify more than one section, you can set a section header’s appearance by responding with a UIView to -(UIView*) tableView: viewForHeaderInSection: in your UITableView’s delegate.

This is a full-fledged UIView, so you can do things like drawing with Core Graphics or adding a complete view hierarchy. For the simple case of an ad, if you already have a PNG with the ad to display, you can simply do:

- (UIView*) tableView: (UITableView*) tableView 
  viewForHeaderInSection: (NSInteger) section 
{
  /* assumes your tableview is 320 wide, makes a section header 80 pixels high */
  customView = [[[UIView alloc] initWithFrame: CGRectMake(0.0, 0.0, 320.0, 81.0)] autorelease];
 
  UIImageView *imgView = [[[UIImageView alloc] initWithImage: myPNGImage] autorelease];
  /* makes the views slightly transparent so you can see the cells behind them as you scroll */
  imgView.alpha = 0.7;
  customView.backgroundColor = [UIColor colorWithRed: 1.0 green: 1.0 blue: 1.0 alpha: 0.7];
  
  [customView addSubview: imgView];
  
  return customView;
}

I made the header with a semi-transparent white background so you can see a little bit of the underlying news as you scroll by, giving the overlay a nicer appearance. You can adjust it to suit your needs.

This is great. The ads stay on-screen, on top of the content, and they don’t interfere with it.

There’s only one problem…
You can’t tap on section headers. There is no delegate method to tell you what section header was tapped.

UIButton to the rescue

What if, instead of embedding a UIImageView, you embedded a UIButton in your customView? UIButton is a subclass of UIView, but it handles events and can dispatch on target-action. Plus, you can add an image to a button! Let’s try this:

- (void) headerTapped: (UIButton*) sender
{
  /* do what you want in response to section header tap */
}
  
- (UIView*) tableView: (UITableView*) tableView 
  viewForHeaderInSection: (NSInteger) section 
{
  customView = [[[UIView alloc] initWithFrame: CGRectMake(0.0, 0.0, 320.0, 81.0)] autorelease];
  customView.backgroundColor = [UIColor colorWithRed: 1.0 green: 1.0 blue: 1.0 alpha: 0.7];
 
  /* make button one pixel less high than customView above, to account for separator line */
  UIButton *button = [[[UIButton alloc] initWithFrame: CGRectMake(0.0, 0.0, 320.0, 80.0)] autorelease];
  button.alpha = 0.7;
  [button setImage: myPNGImage forState: UIControlStateNormal];
  
  /* Prepare target-action */
  [button addTarget: self action: @selector(headerTapped:) 
    forControlEvents: UIControlEventTouchUpInside];
  
  [customView addSubview: button];
  
  return customView;
}

The difference with UIImageView is the target-action method. We added a new target (self), with the method headerTapped: (defined above) for the event UIControlEventTouchUpInside. You can view all events handled by UIButton, but I recommend TouchUpInside because it is sent when the user lifts her finger while inside your button. If she slides out of the button the action is not sent and she can effectively “cancel” the button’s activation.

Voi|à! You now have a section header that responds to taps.

Notes

This code uses hard-coded numbers for example purposes. Use macros or consts in your own code, not magic numbers!

Also, creating custom UIViews is expensive. You don’t want to do it every time viewForHeaderInSection: is called as this will impact performance. Once you create a UIView, save it in a mutable array or dictionary and cache it for the next time you are called.

Categories: Development, MacOSX Tags:

Automatically localize your nibs when building

December 22nd, 2009 21 comments

This post applies to all Cocoa apps, whether on the desktop or on the iPhone.

When you want to localize your application, you can take several routes.

No nibs, only .strings files

The first one is to not use nibs (or xibs, in the new parlance). If you build everything programmatically, +alloc and -init-ing your controls and your views, you can store all the strings in .strings files. These files are simple key-value pairs of Unicode-encoded strings. Call the files “Localizable.strings”, drop them in the appropriate language folders (English.lproj, French.lproj, etc…).

	English.lproj/
		Localizable.strings
			[file contents]
			"Dawn" = "Dawn";
			"Sunrise" = "Sunrise";
			"Sunset" = "Sunset";
			"Dusk" = "Dusk";
	French.lproj/
		Localizable.strings
			[file contents]
			"Dawn" = "Aube";
			"Sunrise" = "Lever";
			"Sunset" = "Coucher";
			"Dusk" = "Crépuscule";

To use, simply call call:

	NSString *str = NSLocalizedString(@"Dawn", nil);

This technique is important even if you have nib files, because most of your strings are probably in .strings files already. Don’t use hard-coded strings for anything that is user-visible. Only use hard-coded keys.

Use nibs

This looks easy: duplicate your English nib, move it to the appropriate folder, open it in Interface Builder and translate all the strings directly in IB.

This is very bad. Don’t do it.

  • It’s unmaintainable: as soon as you change one thing in the original nib, you have to do it in one or more other nibs.
  • It’s error-prone: you can easily disconnect a binding without realizing it, and Undo support in IB is spotty at best (I don’t rely on it). By the time you realize your mistake, it may be too late and you have to revert everything.
  • It’s not versionable: although xib files are XML files, they are not meant to be user-editable. Think of them as binary files. You wouldn’t merge binary files now, would you?
  • It’s hard to do: there are many nooks and crannies where localizable strings are found in IB (tooltips?), you’re bound to forget some of them in some language.
  • It needs a developer: you would not hand off a nib file to a translator and expect him to know all of the above: there is some training involved. And even if you have the Fear of God when editing nib files (because you’ve been burnt before by a disconnected IBOutlet), chances are your translator has no such qualms.

Translators want strings files, not nib files

You could use Wil Shipley’s approach of translating everything at run-time. That approach has one great advantage: everything is driven by strings files, which only contain strings (duh!). Your translator(s) will do a great job with them, and you can easily diff the results in your own version control system when the translations come back.

There are, however, drawbacks to this approach. Since everything happens at run-time:

  • You have to ship and execute more code than necessary (this is a minor point, but still valid for the iPhone since it is very resource-limited).
  • You can only see the results by launching your app in the appropriate language, switching using the International Settings panel, which is tedious.

A compile-time approach

My approach expands on Brian Dunagan’s use of ibtools and genstrings.

Xcode has excellent build scriptability due to Run Script build phases. In this example, we will use a script to generate a localized French xib from an English xib, using a strings file as a template for translation.

First, create a French version of your xib file by adding a French localization to your English xib file.
Assuming it is called “MainWindow.xib”, select the xib file, choose File > Get Info and in the “General” pane click “Add Localization”. Type “French”.

This will create the “French.lproj” folder, which will contain a copy of the English MainWindow.xib.

Next, add a Run Script phase to your application target:

Run Script build phase in Xcode

Finally, enter this as your script:

# Extract English strings (use this to check if you added new strings to your nibs)
ibtool --generate-strings-file Resources/English.lproj/MainWindow.strings Resources/English.lproj/MainWindow.xib
# Generate French interface
ibtool --strings-file Resources/French.lproj/MainWindow.strings --write Resources/French.lproj/MainWindow.xib Resources/English.lproj/MainWindow.xib

Repeat each pair of lines for each xib you need to localize, and adjust the names accordingly.

The first command extracts all the localizable strings from your English xib, and stores them in English.lproj/MainWindow.strings. This is your reference file: add it to version control, but you do not need to add it to your application. You can add it to your project, but make sure it is not included in your app bundle (it is useless at runtime).

The second command takes a French-localized version of the same strings file (French.lproj/MainWindow.strings) and, using the English xib as a template, generates the French xib.

Wait a moment…

If you followed so far, build your app. The script should fail, because French.lproj/MainWindow.strings does not exist yet. Just make a copy of English.lproj/MainWindow.strings and put it in the French folder. Just like the English MainWindow.strings, you want to add this file to version control and your project, but not to your app’s resources.

If you build again, everything should go fine and your French MainWindow.xib should be created… in English.

Translation

Of course, you have to translate the French MainWindow.strings. Mine looks a bit like this:

/* Class = "IBUIBarButtonItem"; title = "Done"; ObjectID = "138"; */
"138.title" = "Terminé";
/* Class = "IBUIBarButtonItem"; title = "Today"; ObjectID = "140"; */
"140.title" = "Aujourd'hui";
/* etc... */

You can send this file to your translator, and instruct her to only translate the words in quotes on the right-side of the equal (“=”) sign. Don’t touch anything else.

But… the Fear of God?

Everything else in this file is necessary for ibtool to do its job properly, and should not be touched. There are two safeguards against you (or your translator) accidentally touching this information:

  1. You have the original English file. When the file comes back from your translator, you can diff it against the original (which is in English, remember?) and see that she has followed your instructions (or not). It should be pretty easy to spot the differences with FileMerge.
  2. Every build, the script re-creates the English MainWindow.strings file. Your version control system should flag any differences immediately. For instance, if you added a label, you would see it in your new English file and you could apply the exact same change to your French file, and make a note to send it for translation again.

I found that these two safeguards more than compensated for the fact that the generated strings file are really source code, since they contain essential information for ibtool to translate properly.

Summary

Since everything happens at compile-time, my solution has none of the drawbacks of Wil’s solution:

  • No extra code needed.
  • You can look at a generated xib file in Interface Builder and immediately see any layout issues you might have. In this case, change the English xib, rebuild and check again.

Remember, these files should be added to version control, but not to your app bundle:

  • English.lproj/MainWindow.strings
  • English.lproj/MainWindow.xib
  • French.lproj/MainWindow.strings

And this file should be added to your app bundle, but not to version control (since it is generated each time):

  • French.lproj/MainWindow.xib

Now, every time you build, you will generate your French interface and it will be added to your app. Simple and efficient.

Categories: Development, MacOSX Tags:

Display std::wstring in Xcode using a Data Formatter

July 21st, 2009 10 comments

std::wstring: unsafe, but sometimes necessary

When dealing with cross-platform code, especially Windows, it is not uncommon to encounter wchar_t and its companion string class, std::wstring.

Unfortunately, Xcode does not display these types natively. Yes, you can add this file to your .gdbinit file and then you will be able to type:

    pwstring mystring

but what you really want is for the values to show up in Xcode’s variable display, and in tooltips.

What you need is called a Custom Data Formatter.

In this post, I will adapt Apple’s sample wchar_t data formatter and modify it to display std::wstring variables. I will also explain where you should put this formatter in Xcode 3.1.

Complete source code is available on bitbucket.org, with a MIT license.

Initial Data Formatter

Xcode Data Formatters are simple bundles that are loaded by Xcode at startup. They are essentially plug-ins to extend Xcode.

Starting with Apple’s own wchar_t Data Formatter sample, we immediately notice that:

  • This sample is quite outdated, almost 4 years old (the Release build actually builds only PPC!)
  • It won’t work as-is with std::wstring since it only contains .c files.

We will update this sample to work with std::wstring and build it as a 4-way universal binary.

Modernizing the Data Formatter

Switching to C++

Since the data formatter will use std::wstring, we need to build it using C++. Just rename all the .c files to .cpp and voilà!

Modify CustomDataViews.plist

If you open CustomDataViews.plist, you will see that it has one entry per variable type that the formatter can handle. That entry contains a Data Formatter, much like the one you would type in Xcode itself (for example, to display the name of a notification—of type NSNotification—you can use {(NSString *)[$VAR name]}).
What is different here is the formatter contains a string that calls back in our custom Data Formatter: for example, wchar_t corresponds to {(char *)myWCharDataFormatter((wchar_t) $VAR, (int) $ID)}:s.
Add two entries to that plist, for types wstring and wstring*, and replace the callback strings with a new unique string, for instance myWStringDataFormatter and myWStringPointerDataFormatter. Watch for capitalization, case matters.

Add new callback functions

Now add the two callback functions you referenced in the plist in myCustomFormatter.cpp:

#include <string>

char * myWStringDataFormatter(std::wstring wstr, int identifier)
{
	size_t bufLen = wstr.size() + 1; 
	return dataformatter_char_for_wchar(wstr.data(), identifier, bufLen);
}

char * myWStringPointerDataFormatter(std::wstring *wstr, int identifier)
{
	size_t bufLen = wstr->size() + 1; 
	return dataformatter_char_for_wchar(wstr->data(), identifier, bufLen);
}

As you can see, we are re-using the preexisting wchar_t code with our std::wstring.

Build a 4-way Universal Binary

Double-click the wchardataformatter project icon to bring up the Project Inspector. Select the Release configuration, and click the “Architectures” pop-up menu. Select 32/64 bit universal, as shown below.

Universal Architecture in Xcode

Modify the Test Code

Finally, update wcharTest_main.cpp to add new std::wstring variables to display in the debugger:

#include <stdlib.h>
#include <wchar.h>
#include <string>

int main(int argc, char *argv[])
{
	wchar_t c = 'A';
	wchar_t b[255];
	wchar_t *d = &b[0];
	char *a = "a b c d e f g";
	mbstowcs(d, a , strlen(a));
	std::wstring s = L"wide string";
	std::wstring *ps = &s;
	return 0;
}

Switch the target to wCharTest in Xcode, set a breakpoint on the last line, and hit “Build and Go”. Xcode should build the test application and stop in the Debugger. Since you have not installed the formatter, you should not see anything special in the Variable Display window.

Installing the Data Formatter

Build the Data Formatter (Debug or Release). Locate the bundle by control-clicking on the “Products / wcharDataFormatter.bundle” in the Xcode project.
Drag the bundle to this folder: ~/Library/Application\ Support/Developer/Shared/Xcode/CustomDataViews, creating intermediate folders along the way if they do not exist.
You can remove the ~ to install for all users (not just you), and you can also replace “Shared” in the path with “3.0” or “3.1” if you only wanted to install the formatter for Xcode 3.0 or 3.1.

Testing the Formatter

Build the wcharTest target, and set a breakpoint in the main() function of wcharTest_main.cpp. You can step over each line, and you should see your data formatter being called and displaying in Xcode’s Variable Display, as well as in tooltips (Xcode 3.1 and later).

Formatter Display in Xcode

Categories: Development, MacOSX Tags:

Open any page or location in your Help Book

June 29th, 2009 Comments off

Anchors

So you wrote your documentation using VoodooPad and you want to open a page of your choice, for instance, the “Quick Start” page.
But you don’t want this page to be the index page.

You know you can open your Help Book with this code:

[[NSApplication sharedApplication] showHelp: nil];

What you want to use is this:

NSString* bookName = [[NSBundle mainBundle] 
                              objectForInfoDictionaryKey: @"CFBundleHelpBookName"];
[[NSHelpManager sharedHelpManager] openHelpAnchor: @"__ANCHOR__" inBook: bookName];

(where __ANCHOR__ is an actual HTML anchor in your Help Book)

Creating the anchors

There are two parts to this. First, create page aliases in VoodooPad and second, update your WebExportPageTemplate to output those aliases.

VoodooPad Page Aliases

Any page in VoodooPad can have one or more aliases. They can be added by clicking the Info button on the Toolbar and clicking on the “+” button at the bottom of the Info panel. You can add as many as you want.

Tip: If you have multiple languages for your Help file, make sure that the aliases match the openHelpAnchor: above. Since this string is not user-visible, it makes sense that it be the same across all Help files.

Modifying the page template

In your VoodooPad document, open the WebExportPageTemplate page.
On that page, insert the following code at a proper location (for instance, just after the <body>tag):

<a name = "$alias$"></a>

$alias$ is a special VoodooPad variable that is replaced by the first alias in the list you defined above. It can also be called $alias[0]$.

If you have more than one alias, and you want to have multiple anchors in your page (for instance, a header and a footer), you can use $alias[1]$, $alias[2]$, etc. But in general, your help pages will be small enough that one anchor/alias per page should be sufficient to open a given page in your Help book, directly from your application.

Indexing

Make sure you actually run the Help Indexer application on your HTML Help folder.

#!/bin/sh
"/Developer/Applications/Utilities/Help Indexer.app/Contents/MacOS/Help Indexer" \ 
               "$VPWebExportOutputDirectory"

This step is necessary for the Help System to find your anchors by name.

Categories: Development, MacOSX Tags:

Day Four: Integrating Help in your application using VoodooPad

May 11th, 2009 3 comments

[Update September 12th, 2009: VoodooPad 4.2.2 fixes the one bug in this post. Thanks Gus!.]
[Update June 28, 2009: there is an issue with the Help Indexer script and Snow Leopard. Contact me for the details, which only matter if you have Snow Leopard.][Update August 30th, 2009: now that Snow Leopard has been released, I updated the Help Indexer script to run with hiutil.]

Documentation

I’m a big fan of using the right tool for the right job, and I also found myself suffering from “blank page syndrome” when it came time to write initial documentation for iChibi.

I really like VoodooPad for its ability to quickly transfer ideas from my head to some kind of structured document, which I am free to revise later.

VoodooPad even has a web export module, which can create a set of pages that are compatible with Apple Help. Excellent! I could now overcome the blank page and start writing some documentation.

The process

If you write all your documentation in VoodooPad, you must follow these steps to create a valid Help folder to integrate in your application:

  1. Export the document to your Help folder.
  2. Open the main page of your document and insert the appropriate AppleTitle and AppleIcon tags.
  3. Drag-and-drop your Help folder to Help Indexer (/Developer/Applications/Utilities/Help Indexer.app), to auto-generate the index.

That’s still a lot of clicking.

Integrating with Xcode

In order to make sure that when I release software, I ship the latest of everything I like to add build phases to Xcode and have it perform all these tasks automatically. I sometimes run these scripts in Release builds only, because I don’t want to waste any time in the compile-link-debug cycle of a Debug build.

Here is the script I use within Xcode. You can use it too, just add a new “Run Script” phase to your target:

Run Script build phase in Xcode

if [[ ${CONFIGURATION} == "Release" ]]
then
	# Set to whatever you have as CFBundleHelpBook in your Info.plist
	HELP_FOLDER="$TARGET_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/English.lproj/Help/"
	HELP_DOC="iChibi Help.vpdoc"
	# Clean folder, so we don't have extra junk in there
	rm -r "$HELP_FOLDER"
	echo "Generating documentation..."
	open "$SRCROOT/doc/$HELP_DOC"
	osascript -e "tell application \"VoodooPad Pro\"" \
		-e "tell document \"$HELP_DOC\"" \
		-e "web export to \"$HELP_FOLDER\" with properties {fileExtension:\"html\"}" \
		-e "end tell" -e "end tell"
fi

(You will want to replace the name and path of your VoodooPad Help document and output folder, of course.)

What this does is delete the current Help folder, and re-generate it using AppleScript to tell VoodooPad to export the proper document to the Help folder.

But wait, there’s more!

In this Xcode build script, there is nothing about setting the AppleTitle or the AppleIcon, nor is there any Help Indexer. Those are all handled by VoodooPad using built-in scripts.

VoodooPad’s Web Export behavior can be overriden by specially-named pages:

  • WebExportPageTemplate
    This page overrides the Export Module selection in the Web Export function.
    This is your basic html page, with extra markup for the actual page content ($page$). The only notable addition I made was to add two comments in the <head> section:

    	<!-- AppleTitle -->
    <!-- AppleIcon -->

    These will be used postflight script below.

  • WebExportPostflightScript
    This page will be run as a shell script with a few interesting environment variables, notably $VPWebExportOutputDirectory. Here is the content of the page:

    #!/bin/sh
    
    # Replace AppleTitle comment in index.html with appropriate value
    perl -pi -e 's/<!-- AppleTitle -->/<meta name=\"AppleTitle\" content=\"iChibi Help\">/' "$VPWebExportOutputDirectory/index.html"
    
    # Replace AppleIcon comment in index.html with appropriate value
    perl -pi -e 's/<!-- AppleIcon -->/<meta name=\"AppleIcon\" content=\"appicon16.png\">/' "$VPWebExportOutputDirectory/index.html"
    
    # Index documentation
    if [ -a "/usr/bin/hiutil" ]; then
      # Using hiutil on Snow Leopard
      /usr/bin/hiutil --create "$VPWebExportOutputDirectory"Help/" --file "$VPWebExportOutputDirectory"Help/Help.helpindex"
    else
      # Using Help Indexer.app
      "/Developer/Applications/Utilities/Help Indexer.app/Contents/MacOS/Help Indexer" "$VPWebExportOutputDirectory"Help/"
    fi
    
    exit 0
    
    

Tip: in the Page Info, check “Skip on Export” for those two files since they are not needed by the Help Viewer.

You can download iChibi’s Help document (VoodooPad format) and use it as a starting point for your Help document.

Bugs to iron out


The build script activates VoodooPad (brings it forward) and leaves the Web Export dialog active. You have to manually dismiss this dialog, or use AppleScript to tell VoodooPad to quit. I don’t like the heavy-handed “quit” approach because I may be working in other VoodooPad documents, and don’t want them to disappear even if they are auto-saved (thanks, VoodooPad!).

There is probably a way to dismiss the dialog using AppleScript and accessibility (e.g. "tell button 3 of dialog 'Web Export' to perform action") but that strikes me as even more of a hack. I hope Gus can fix it in an upcoming release of VoodooPad :-).

This is fixed in VoodooPad 4.2.2.

Categories: Six Days of Cocoa Tags:

Day Two: iChibi and ImageSoup

April 20th, 2009 Comments off

Six Days of Cocoa: Day Two

Day Two: code-name iChibi

My whole family is very much into all things japanese (including, of course, manga and anime). The other day, they found a little “sound-playing ghost” called Flele (I have no idea what the name means).

As far as I can tell, Flele is a small application that you drop one or more MP3s on, and it starts to “sing” when you click on the character’s hair. I have no idea how it actually works, since it requires Windows and the Japanese language pack. But my daughter and I quickly hashed out that what we could do would be a globally-floating iTunes controller, who would respond to click and animate when music was playing (we’ll figure out the “singing” part later).

iChibi

My daughter drew lots of PNGs this weekend (using Painter, of course), and we assembled them in sequences to make animations. The iChibi can blink, and lights turn on/off on its headphones whenever music is playing.

iChibi can be dragged anywhere on screen, and will float on top of all windows.

Clicking on its left ear starts playing in iTunes, on the right ear stops playing. The left headphone goes to the previous track, the right headphone to the next track.

Clicking on the zipper gives a Settings panel, where you can control the its size, opacity and which playlist the songs are coming from.

The higher the song rating, the happier it will look.

iTunes support is done through the excellent Eyetunes Framework.

iChibi works on MacOSX 10.5 and later.

If you are interested in following the development of iChibi, follow @ichibiapp on Twitter.


ImageSoup

Since iChibi uses lots of images for animations, and I didn’t want to keep multiple copies of the same image in memory (for example, if an image is re-used in multiple animation loops), I figured I should make a small class to hold all the images, loading them as necessary. I named this class ImageSoup, referring to the Newton’s filesystem of course…

ImageSoup is very simple, and does not need to be its own class (after all, it’s a dictionary). But it is nice to be able to abstract that implementation detail out of your code, as well as the image-loading code. What if your source image was an Acorn image or a Painter RIFF? You could hide the image-loading code in ImageSoup, and your calling code need not be aware of this.

To use ImageSoup, just create an ImageSoup* instance and always ask it to load your images (using the full path to the image):

#import "ImageSoup.h"

[...]

ImageSoup* allImages = [[ImageSoup alloc] init];

[...]

NSImage* myImage = [allImages getImageAtPath: @"Full/path/to/image"];

If the image exists and has been loaded previously, ImageSoup will return it right away. If the image does not exist, ImageSoup will load it and store it for future reference.

When you release your ImageSoup*, all its images will be released.

ImageSoup.h

//
//  ImageSoup.h
//  iChibi
//
//  Created by Philippe on 09-04-19.
//  Copyright 2009 Philippe Casgrain. All rights reserved.
//

#import <Cocoa/Cocoa.h>

@interface ImageSoup : NSObject
{
  NSMutableDictionary* _images;
}

- (NSImage*) getImageAtPath: (NSString*) path;

@end

ImageSoup.m

//
//  ImageSoup.m
//  iChibi
//
//  Created by Philippe on 09-04-19.
//  Copyright 2009 Philippe Casgrain. All rights reserved.
//

#import "ImageSoup.h"

@implementation ImageSoup

- (id) init
{
  self = [super init];
  if (self != nil) 
  {
    _images = [[NSMutableDictionary dictionaryWithCapacity: 0] retain];
  }
  return self;
}

- (void) dealloc
{
  [_images removeAllObjects];
  [_images release];
  [super dealloc];
}


- (NSImage*) getImageAtPath: (NSString*) path
{
  NSImage* retrievedImage = [_images valueForKey: path];
  if (retrievedImage == nil)
  {
    NSImage* image = [[NSImage alloc] initWithContentsOfFile: path];
    if (image)
      [_images setObject: image forKey: path];
    [image release];
    retrievedImage = image;
  }
  return retrievedImage;
}

@end
Categories: Graphics, Leopard, Six Days of Cocoa Tags:

Day One: Daylight

April 6th, 2009 3 comments

[Update 04/08/2009 – Daylight is available on the App Store. Have a look, it’s free!]
[Update 19/07/2009 – Daylight has been submitted to the App Store!]
[Update 19/07/2009 – Twilight is now called Daylight.]

Six Days of Cocoa: Day One

I found myself with six unexpected days off, so I decided to take them on six consecutive Mondays, when the kids are in school and most of the housework is done, to concentrate on my independent Cocoa projects. These are projects that I started but put on the back burner for lack of “quality time”.

Day One: Daylight

Daylight is an iPhone application that I wrote to scratch an itch: when does the sun rise or set every day? It’s important to me because I bike to work year-round, and cars can see me much better at dusk than they can at night.

It’s also useful for photographers and filmmakers. One hour before sunset is the so-called “golden hour“, where the shadows are long and the scenery is tinted with an amber glow. Dusk and dawn also form the “blue hour“, much more important at higher latitudes, where there is no direct sunlight; everything is diffused through the atmosphere. No shadows, no glare, no overexposure…

Daylight is extremely simple. It uses Core Location to determine where you are in the world, and uses the internal clock to figure the current time, and offset from GMT.

There are only a few settings in Daylight . You can choose between Civil, Nautical and Astronomical twilight, set the date (defaults to Today) and reset your location (which is cached for 30 days by default).

Daylight is perhaps the very definition of a one-shot app: you launch it, it does what it says, and you’re done. It encourages discoverability by having large buttons and a little bit of animation.


Today, I found the one bug that was preventing me from going forward, so I am looking for beta-testers for Daylight . If you are interested, please send me a direct message on Twitter (d daylightapp) or send an email to daylight-beta@casgrain.com with your device’s identifier.

Categories: Six Days of Cocoa Tags: