Cocoa
Custom Drawing Using drawRect, Part 1
Dec 18th
One of the more advanced techniques for creating custom user interfaces on the Mac is the use of NSView’s drawRect method. Many answers to questions on StackOverflow and Apple’s mailing lists include recommendations to “just override drawRect and do the drawing yourself”. Some folks see this recommendation and their eyes glaze over, thinking that it’s too advanced of a technique for them to wrap their heads around. Over the next few days I’m going to go over some basic techniques that can yield powerful results.
Let’s start by setting up the Xcode project that will be the basis of the rest of these posts.
- Open Xcode and create a new Cocoa Application project called DrawingSample.
- Create a new NSView subclass called CustomDrawingView.
- Open MainMenu.xib, add a new Custom View to the Main Window, set its class to be CustomDrawingView, and set it’s autosizing flags as seen here:
Save and Quit Interface Builder and switch back to Xcode. Open CustomDrawingView.m, it should look like so:
@implementation CustomDrawingView - (id)initWithFrame:(NSRect)frame { self = [super initWithFrame:frame]; if (self) { // Initialization code here. } return self; } - (void)drawRect:(NSRect)dirtyRect { // Drawing code here. } @end
We’re going to start (and finish) today with just a simple concept and some basic drawing that will set the stage for the future posts. All drawing in Cocoa is done by first setting up the environment in which you want to draw, and then doing the actual drawing. For instance, if we want to draw a blue box, we first have to setup the color blue, define the bounds of the box, and then draw it. In this case we are using the NSRect that is passed to the drawRect method as the box we want to draw, and we setup the color blue by calling [[NSColor blueColor] set]. We then use the convenience method NSRectFill to fill the dirtyRect with the color blue. Notice that we didn’t pass the color to NSRectFill, we set it, and from then on anything we draw will be blue until we change the color.
You can think of drawing in Cocoa much the same way as you would think of painting with a brush. You dip your brush in a certain paint color, paint the shape you want to paint, and then dip your brush in a new color and paint some more.
- (void)drawRect:(NSRect)dirtyRect { [[NSColor blueColor] set]; NSRectFill(dirtyRect); }
The preceding code, when run, will generate a view that looks like this:
Now, this may not look like much, but in future posts we will build on these concepts and, hopefully, by the end have drawn some pretty cool and useful things.
Bottom Bars in Interface Builder
Dec 18th
Dave posts a handy tip for those that may not know that there’s a non-code way of setting up the bottom bars on your windows. Check it out here: Bottom Bars in Interface Builder – Dave Dribin’s Blog.
Core Data Code Generation
Sep 9th
Bill Dudney has a great post about using categories to avoid some of the work when generating and regenerating classes from your Core Data model. Read about it on his blog, PrEV, here: Core Data Code Generation.
Your New Friends: Obj-C Associated Objects
Aug 28th
Andy Matuschak has posted a little-known feature of the Objective-C API under Snow Leopard on his blog. I can’t think of a use for associated objects at the moment, but there’s a good chance that I will need them in the future. You can read about them at his blog: Square Signals : Your New Friends: Obj-C Associated Objects.
UIView Manipulation Made Easier with a Category
Jul 27th
I was watching a presentation recently where the presenter showed the header for a category that he had added to UIView to make his life a little easier. The point of the talk did not center around the category so I never saw much more than the header, but that was all I needed to recreate it for my use. Here’s the header:
#import <uikit/UIKit.h> @interface UIView (MFAdditions) - (id) initWithParent:(UIView *)parent; + (id) viewWithParent:(UIView *)parent; // Position of the top-left corner in superview's coordinates @property CGPoint position; @property CGFloat x; @property CGFloat y; // Setting size keeps the position (top-left corner) constant @property CGSize size; @property CGFloat width; @property CGFloat height; @end
As you can see there isn’t a whole lot to this category, but if you’re doing a lot of view manipulation the benefits of it will rapidly become clear. There was one sticky wicket that I hit when implementing this class and it centers around this method:
+ (id) viewWithParent:(UIView *)parent;
When I first implemented this method I wrote it like so:
+ (id) viewWithParent:(UIView *)parent { return [[[UIView alloc] initWithParent:parent] autorelease]; }
This was all well and good as long as the only class that I was creating was a UIView, but I ran into trouble when I started creating UIImageViews. Instantiating new UIImageViews worked fine, but as soon as I called a UIImageView-specific method the app would crash:
*** -[UIView setImage:]: unrecognized selector sent to instance 0xd1b350
I struggled with the answer to this one for a while and it wasn’t until I presented my problem to the local CocoaHeads group did I get it all figured out. Here’s the correct way to write this method:
+ (id) viewWithParent:(UIView *)parent { return [[[self alloc] initWithParent:parent] autorelease]; }
By calling self instead of strongly typing the returned object as a UIView the class would dynamically determine the correct type at runtime.
You can download this category here: UIViewAdditions
Quick and Easy Drawing Performance Debugging with NSShowAllDrawing
Jul 10th
While watching one of the WWDC09 session videos I was informed of a great tip that I had been previously unknown to me: Pass -NSShowAllDrawing YES as an argument to your application in Xcode to see a visual representation of the drawing that your application performs as it runs.
To illustrate how NSShowAllDrawing works and the issues it can help you correct I’ve put together two videos. The first shows my app, Bezipped, in its current 1.0 state and its current drawing behavior.
This second video shows how I improved the drawing in Bezipped simply by setting the top-level container to be backed by a Core Animation layer:
I highly recommend giving your app a spin with NSShowAllDrawing if you haven’t already, it was certainly a real eye-opener for me. There are some additional resources for debugging your drawing performance on OS X (as pointed out to me by André Pang) provided by Apple here: Drawing Performance Guidelines: Measuring Drawing Performance
Lastly, both Alan Rogers and Steve Streza pointed me towards Quartz Debug.app (included with the developer tools) as another means to see similar redrawing behavior. I found Quartz Debug’s options to be a bit heavy-handed as the drawing performance of the entire OS was shown instead of just my app, but your mileage may vary.
The Best Interface Builder Layout Ever
Jul 7th
This morning I posted a screenshot of my Interface Builder layout on Twitter. I didn’t think much of it at the time, but I received enough positive feedback on it that I decided to post it here for generations of future Cocoa developers to find. The layout gets harder to work with the smaller your screen gets, but it works very well on my 24″ display. Another tip for working well with IB: keep it in it’s own space and don’t let other apps invade that space. I find that when I can concentrate just on my IB windows without having to mentally block out background windows it makes my workflow much smoother.
ParseKit
Jul 6th
While I don’t have an immediate need for ParseKit, I’m fairly certain that it’s going to come in very handy in the future. Written by Todd Ditchendorf, the framework offers:
- String Tokenization via the Objective-C PKTokenizer and PKToken classes.
- Language Parsing via Objective-C – An Objective-C parser-building API (the PKParserclass and sublcasses).
- Parser Generation via Grammars – Generate an Objective-C parser for your custom language using a high-level grammar syntax (similar to yacc or ANTLR). While parsing, the parser will provide callbacks to your Objective-C code..
Head on over to the ParseKit website to download it.
Bypassing the Trouble Caused by Updating an iPhone App Provisioning Profile
Jun 19th
[UPDATE] After many discussions on Twitter and many recommendations by different folks, I think that we have determined that the method outlined below is not necessary. Mike Taylor has hit upon what appears to be a foolproof method for getting around the trouble caused by updating an iPhone app provisioning profile, and best of all, he did it in 140 characters:
@kalperin @MrRooni Delete old profile from Organizer. Download new profile ‘n drag to organizer. Restart Xcode. Choose new profile in target
Many thanks to Mike for helping me out here, I owe you a beer at WWDC10. For those of you interested in the more masochistic way to get around the issues, feel free to continue reading:
A note before I begin: Everything below was done with the final build of the iPhone OS 3.0 SDK and I was building an app using the 2.2.1 frameworks.
I recently acquired a new iPod touch to use a development device. One of the first things that I wanted to do was get my existing project up and running on the device so I headed over to the iPhone Development Program Portal to update my provisioning profile.
After adding the new device to my app’s provisioning profile I downloaded the updated profile and installed it into Xcode and onto the device via the Organizer. So far so good, everything up until this point worked exactly as I expected it to.
Switching back to my project I changed my build settings to be a Release build under 2.2.1 on the device. The app built fine, but I got an error when it tried to install the app onto the device, something error akin to “This device doesn’t contain the provisioning profile with which this app was built”. Thinking that maybe Xcode just hadn’t seen the new profile yet I cleaned all targets in my project, restarted Xcode and tried again. No dice, same error as before.
I then proceeded to delete all versions of the profile from the organizer and re-installed the new one. My assumption was that once Xcode saw that the old profile didn’t exists anymore it would switch over and use the new one. I cleaned all targets and built again. This time I was treated to a different error: ”Code Sign error: Provisioning profile ‘3E6AA725-6534-46F8-B9CE-D19AC9FD854B’ can’t be found”
After a bit of Googling I discovered that Xcode stores the ID of the provisioning profile in its project.pbxproj file. This discovery led me to the fix:
- Close your Xcode project
- Navigate to your project folder in the Finder
- Right click on your .xcodeproj file and ‘Show Package Contents’
- Drag the project.pbxproj file to Xcode (or any plain text editor)
- Perform a search for the term ‘provision’ to find the PROVISIONING_PROFILE entry.
- Copy the existing profile ID and paste it into the find field of a find and a replace dialog.
- Open up the Organizer window (Window menu > Organizer) and navigate to your new profile under IPHONE DEVELOPMENT > Provisioning Profiles
- Click on your provisioning profile and copy its Profile Identifier
- Paste the string into the replace field in your open find and replace dialog.
- Replace all instances of the identifier, save the file, close it, and reopen your Xcode project.
- That should do it, build and go to run your app on your new device.
Now there is a great possibility that I am going WAY overboard here and missing a very obvious way to accomplish the same solution. If that’s the case please let me know in the comments.
iPhone, Timezones, and WWDC (or, How I Stopped Worrying and Learned to Love iCal)
May 21st
At 9:53 AM EST this morning I asked the Twitterverse the following:
WWDC iPhone question: When I land in SF will my calendar events be localized such that I won’t be showing up for things 3 hours late?
Thanks to @LarryRoth and @jeff_lamarche I have my answer.
According to Larry,
Yes. I travel all the time and it just works. Your calendar should be set to EST and when you land your phone will know it’s PST.
And according to Jeff,
You’ll probably need to manually set your computer to the right time zone, or you’ll be off by 3 hours.
What this means is that any events that I add to my iCal calendar will be shifted three hours down when I land in sunny San Francisco. Therefore, when I add the sfMacIndie party to my calendar (it starts at 5pM PST) I need to add it at 8PM on my calendar while I’m in the EST time zone. See the two images below for how my event times change as my time zone changes.
One recommendation that I can make to ease all of your pain is to use a service like TripIt. My trip to WWDC was my first use of TripIt, and it has been wonderful. Basically, it manages all your trip information in one easy place and even provides you with an iCal subscription link so that you can have your entire trip in iCal. Once I had my flight and hotel confirmations I just forwarded them to TripIt and it automatically parsed the information out and created an itinerary from it. From there I was able to add events manually to it (like the sfMacIndie party and WWDC badge pickup) and they were automatically entered into my calendar. The best part is that I didn’t have to do any time zone math, I just entered the events into TripIt at the time they are occurring in PST and it handled making sure that they showed up properly on my calendar




