I gave a presentation a few weeks ago at the local CocoaHeads MN meeting on extending Xcode. It was pretty well received and at some point I promised to deliver this content online in some form; this post is the result. The techniques here are all available in some form or another elsewhere on the web, but it took some digging to get them together in one place and working in Xcode 3.1. Hopefully this post will save some of you from having to do similar work, which while fascinating, may not be the best use of your time.
It should be noted that I am far from the most proficient Xcode user on the planet. There may be better ways to approach the following; if I’ve made a mistake somewhere please leave a comment to that effect and I’ll correct it.
I will be covering three ways to extend Xcode. First custom project and file templates, then the really useful custom text macros with placeholders.
All of this work was done in Xcode 3.1. It was not tested with earlier versions and may not work there.
Custom Project Templates
Xcode comes with some great templates to get you up and running quickly with a variety of common application types. Early on in my iPhone development work when I was churning through a bunch of project ideas I found myself making the same modifications for code signing and basic setup again and again. Exactly the kind of work that I would normally automate with a build tool. I wanted to build Xcode project templates that had a number of these things baked in. It turned out to be pretty simple, once I got the directory structure worked out.
I’m going to walk through the following steps.
- Create an override directory for Xcode
- Copy and modifying an existing Project Template
Creating a User Override Directory
When Xcode boots it scans through a few different directories to put together its runtime environment. One of the last directories that it scans is
~/Library/Application Support/Developer/Shared/Xcode. This directory contains user specific Xcode extensions. If this directory does not exist, you will have to create it. This is where you will place project templates, file templates, and macro specifications.
Copying and Modifying an Existing Template
Xcode has a few different directories where it keeps overrideable runtime settings. For our purposes the most useful of these directories are
/Developer/Library/Xcode/ (standard Xcode templates, plugins, etc) and
/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode (iPhone specific extensions). I’ll use the iPhone directory for my examples. It should look like this:
The directory we’re interested in for now is the Project Templates directory. If you open up that directory you will see ‘Application’. Underneath that directory you will find the actual project template directories. These are just standard Xcode projects with a few small differences. Copy one of those folders, for example ‘iPhone Navigation-Based Application’.
Now create a Project Templates directory in your user overrides folder. Create a subfolder named whatever you like; I used Ideaswarm. Paste the project directory into this subfolder. Your directory should look like
~/Library/Application Support/Developer/Shared/Xcode/Project Templates/YourGroupingName/iPhone Navigation-Based Application. Now go to Xcode and choose file -> New Project.
Notice the ‘User Templates’ section with the ‘
YourGroupingName‘ grouping. This grouping will be named after the grouping directory under Project Templates. The template itself will be named after the Xcode project directory; renaming this folder from iPhone Navigation-Based Application will change the name of the template.
This project can now be opened like any Xcode project. Any changes you make will be reflected in the final project. Just remember to delete the build directory that Xcode will create when you open the project; that probably isn’t something you want to include in your template.
One other thing to note is the replacement of certain character sequences when a project is created from a template. The most important of these is
_PROJECTNAMEASIDENTIFIER_ which will be replaced with the new project name wherever it is found, including in file names.
Custom File Templates
Custom file templates are basically the same. You can copy an existing file template from
/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/File Templates/Cocoa Touch Classes. For this example I am using NSObject subclass.pbfiletemplate. Create a directory called File Templates under your user overrides directory. Create another grouping directory inside this directory, named whatever you’d like, and paste the file template into this directory. You’ll end up with something like
~/Library/Application Support/Developer/Shared/Xcode/File Templates/
/NSObject subclass.pbfiletemplate. The ‘.pbfiletemplate’ is required.
Inside of this directory you will find three files:
If you open up TemplateInfo.plist you will find the following three key value pairs:
MainTemplateFile = "class.m";
CounterpartTemplateFile = "class.h";
Description = "my test template";
is the main file to create. CounterpartTemplateFile
is an optional second file. Description
is the description that will appear in the new file window. So if you change description to ‘my test template’ and open the new file dialog you will see a User Templates section with a grouping named
. Selecting this will show your file template.
Again, the name of the template directory (before the .pbfiletemplate part) will become the name of your file template.
If you read some of the existing file templates you can find some constants that will be replaced when the file is created. These will allow you to insert the new file name into the generated text (
<<FILEBASENAMEASIDENTIFIER>>), for instance.
Custom TextMate – Like Macros
One of the things I missed most about TextMate was the easy tab based macro completion with placeholders. It turns out that Xcode actually has something similar, but it is relatively painful to learn how to add your own macros. I’ll be covering how to do this in this section.
The first thing you’ll want to do is take note of the code completion keys that you have set up in the Xcode preferences. These can be found under Xcode preferences -> Key Bindings -> Edit -> Next Completion, Completion List, and Select Next Placeholder. I remapped these to be on my home row because I use them so much.
To follow the pattern of the rest of this post, I will be taking some existing Xcode macros and bending them to my evil will. First create a Specifications directory in your user override directory:
~/Library/Application Support/Developer/Shared/Xcode/Specifications. Then you’ll need to find the xcode macro specifications. These are in
/Developer/Applications/Xcode.app/Contents/PlugIns/TextMacros.xctxtmacro/Contents/Resources/. Copy the ObjectiveC.xctxtmacro file into your new Specifications directory. Any changes in this file will override the previous macro definitions; you are free to change existing macros or add more to your hearts content.
If you open your copy of ObjectiveC.xctxtmacro you will see a long list of declarations similar to this:
Identifier = objc.try;
BasedOn = objc;
IsMenuItem = YES;
Name = "Try / Catch Block";
TextString = "gibberish";
CompletionPrefix = "@try";
IncludeContexts = ( "xcode.lang.objc.block" );
- unique id for the macro. Doesn’t matter what this is, as long as it is unique.
BasedOn - This is a simple inheritance scheme. You can base one macro on another and any keys that are not declared will be pulled from the parent. Usually you will leave this as objc.
IsMenuItem - Determines whether or not this macro will show up in the Edit -> Insert Text Macro menu. Most of the time I set this to NO.
Name - This is the name the macro will appear under in the Edit -> Insert Text Macro menu if IsMenuItem is yet. Usually I put something descriptive in here regardless of whether it is a menu item, to make it easier to determine the purpose of the entry.
TextString - This is the string that your macro will expand to. You can ignore the funky syntax for now, I will explain it shortly. I’ve replaced it with ‘gibberish’ here, for clarity.
CompletionPrefix - this is the basic macro. If you type @try and then hit Next Completion, Xcode will replace this text with the TextString.
IncludeContexts/ExcludeContexts – these allow you to determine where your completion is valid. These are not necessary; if excluded your completion will work anywhere in an objective c file. In the above case, @try will only expand inside of a block.
Ok, lets create a very simple macro. Scroll to the bottom of this file and insert the following (make sure there is a comma after the previous entry):
Identifier = objc.asimplemacro;
BasedOn = objc;
IsMenuItem = NO;
Name = "My first Simple Macro";
TextString = "NSString *something = @\"Simple\"";
CompletionPrefix = simple;
Notice that the double quotes must be escaped. As simple as this is, it is what I get tripped up on the most. If Xcode encounters a parsing error like an unescaped double quote it will either 1) not load your file, using the original macros or 2) not load the macros after the error, resulting in some working and others not. It can be a bit frustrating to track down.
Now restart Xcode (every macro edit requires a restart), open an objective c file, and type ’simple’. Then use your ‘Next Completion’ key. It should replace the text with your NSString. In some cases you may have to go through a few completions to see your macro; this list includes function and variable name completions as well. If you keep your macros fairly unique, you won’t have this problem.
Now we will insert a custom placeholder. Replace the TextString line above with:
TextString = "NSString *<#varname#>= @\"<#text#>\"";
Restart Xcode and try your macro again. This time it should have two placeholders, varname and text. You should be able to jump between them using the Next Placeholder key.
And that’s it! Not quite as nice as TextMate, but a lot better than not having this functionality at all.
More Advanced Macro Techniques
Two other things I wanted to mention briefly.
First, if you use the same CompletionPrefix for two macros then Next Completion will cycle through them. This can be useful for defining two similar expansions with the same keyset. For Example, I use the obscure ‘df’ to first declare a CGFloat, then a float.
Second, IncludeContext and ExcludeContext. These allow you to define the same macro key and have it do different things in different places, for example the body of a function vs the function name, or the header vs the implementation. Some contexts I find useful:
‘initializer’ is after an equals expression. The others are fairly self explanatory. Or seem so to me, after looking at them for so long.
That concludes our Xcode fun for the day. Let me know if you have any questions or need clarification on some points. Hopefully this helps some of you have a more enjoyable Xcode experience.