Isaac Overacker

maker of things

Creating an Xcode Plugin: A Quick-Start Guide

Xcode has a rich ecosystem of third-party plugins, exposed and cataloged by the wonderful Xcode plugin Alcatraz. Some of my favorites: Auto-Importer, BBUFullIssueNavigator, and FuzzyAutocomplete.

However extensive the Xcode plugin catalog may be, there are surely other useful plugins to be written. The process of writing a plugin is not very well documented, and frustratingly, much of the code required to interact with Xcode is still private. However, thanks to the efforts of a few clever developers, getting started is much easier than it used to be. Read on for a quick-start guide to creating an Xcode (5+) plugin.

Prerequisites

First, if you haven’t already installed Alcatraz, do so now. Once Alcatraz is installed and ready to use, install the Xcode Plugin template.

Once the Xcode Plugin template has been installed, create a new project and select the Xcode Plugin OS X template. As much fun as it would be to get started, there’s one last prerequisite to take care of.

Create a new Podfile (What’s that? Go here first.) and install the DTXcodeUtils pod.

Sample Podfile
1
2
3
4
source 'https://github.com/CocoaPods/Specs.git'
platform :osx

pod 'DTXcodeUtils'

After installing the pod, you’ll be ready to get started writing your Xcode plugin.

Creating a Plugin

Open the workspace generated by Cocoapods, and then open the only .m file, which will be named the same as your project name. (Note: the build may fail with a linker error. If this happens, edit your scheme, select Build, and add Pods to the top of the list.) You will see enough boilerplate code to hook your plugin into Xcode and show an alert window when your plugin is used. I’ve included the code sample below with the addition of some code to define a keyboard shortcut for the menu item.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- (id)initWithBundle:(NSBundle *)plugin
{
    if (self = [super init]) {
        // Reference to plugin's bundle, for resource access
        self.bundle = plugin;

        // Sample menu item, nested under the "Edit" menu item.
        NSMenuItem *menuItem = [[NSApp mainMenu] itemWithTitle:@"Edit"];
        if (menuItem) {
            [[menuItem submenu] addItem:[NSMenuItem separatorItem]];
            NSMenuItem *actionMenuItem = [[NSMenuItem alloc] initWithTitle:@"Do Action"
                                                                    action:@selector(doMenuAction)
                                                             keyEquivalent:@""];
            [actionMenuItem setTarget:self];
            // Set ⌃⌘X as our plugin's keyboard shortcut.
            [actionMenuItem setKeyEquivalent:@"x"];
            [actionMenuItem setKeyEquivalentModifierMask:NSControlKeyMask | NSCommandKeyMask];
            [[menuItem submenu] addItem:actionMenuItem];
        }
    }
    return self;
}

- (void)doMenuAction
{
    NSAlert *alert = [[NSAlert alloc] init];
    [alert setMessageText:@"Hello, World"];
    [alert runModal];
}

You’ll probably want to rename your menu item, change your keyboard shortcut, and rename the doMenuAction method, but other than that, we have a great start on our plugin. Run the project, and Xcode will bring up a new Xcode window with your plugin installed. Don’t confuse this new Xcode window with the Xcode window you used to launch the process. Just test your plugin here and then close the window.

Messing with Xcode

A plugin that displays an alert is not very useful. Any good plugin will do something with your code to save you time, and to do that, we need to access the code editor. This is where the DTXcodeUtils library comes into play. A simple but useful scenario is to replace the selected text in the code editor, so let’s explore how to accomplish that with the methods that DTXcodeUtils exposes.

First, import a few of the useful headers from the DTXcodeUtils project.

1
2
#import "DTXcodeHeaders.h"
#import "DTXcodeUtils.h"

In the doMenuAction method, we can now get the selected text from the code editor and replace it with something more…useful.

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)doMenuAction
{
    // This is a reference to the current source code editor.
    DVTSourceTextView *sourceTextView = [DTXcodeUtils currentSourceTextView];
    // Get the range of the selected text within the source code editor.
    NSRange selectedTextRange = [sourceTextView selectedRange];
    // Get the selected text using the range from above.
    NSString *selectedString = [sourceTextView.textStorage.string substringWithRange:selectedTextRange];
    if (selectedString) {
        // Replace the selected string with a comment.
        [sourceTextView replaceCharactersInRange:selectedTextRange withString:@"// Malkovich Malkovich Malkovich"];
    }
}

Now, run the project. In the resulting Xcode window, select some text and run the plugin. (Warning! This does not use the undo buffer, so be careful.) If everything went smoothly, the text you selected should have been replaced with an inane comment. Completely useless, I suppose, but it does demonstrate how to replace the selected text in the code editor!

Installation and Removal

Simply running your project will install the plugin, but if you want to install the plugin manually, you can drop the .xcplugin file in the following directory:

~/Library/Application Support/Developer/Shared/Xcode/Plug-ins

Uninstalling a plug is a simple as removing the .xcplugin file from the above directory. Once you have a great plugin to share with the world, submit your package to Alcatraz to make it simple for others to install your plugin.

Sample Project

The sample code for this post is available on GitHub.

Comments