Xcode Extension 1/5 — Starting Guide

KC Chen
8 min readApr 16, 2021

--

How to expand the usability of Xcode extension by communicating with the containing app

This series contains 5 chapters

  1. Xcode Extension 1/5 — Starting Guide
  2. Xcode Extension 2/5 — Customization setting & POC
  3. Xcode Extension 3/5 — interactive UI
  4. Xcode Extension 4/5 —Intercommunication
  5. Xcode Extension 5/5 — Common Ask Questions

Through these tutorials, you will master Xcode extension without limitation since XcodeKit was known as very limited extension environment. After some research and experiment, the previous description is not so true. We’ll have more explanation later.

All tutorials are base on:

  • Xcode: Version 12.4 (12D4e)
  • Swift: 5.x

Create a project — BetterExtension with “Source Editor Extension”

Create BetterExtension Project

To make a Xcode extension, you need to create a new cocoa app project. Some people may think of an extension project, but it’s really not the way you think.

The next step is adding a “Source Editor Extension” target for BetterExtension which is similar to most of other extensions (today, share…). Those extensions are running in a standalone sandbox (or instance) and only XcodeKit referenced by default. That’s why most of people think the Xcode Extension so limited.

Add a new target
Select “” Xcode Source Editor Extension” for target
Choose the name for your Extension

It’s tricky here for the name of your extension. As you can see its identifier is under your app, it’s a module under your app. The name will affect

  1. Module folder name in your Xcode project
  2. Module name(identifier, white space will be replaced by “-”)
  3. Extension name displayed under “Editor” / “Extension name” in your Xcode menu. In production, it’s the name that your user see it.
  4. Target name

For items under your extension, you may populate custom item’s name differ from those defined in info.plist to overwrite. But there is no way programmatically change your extension name. You may want “Better Extension” for product name, “Extension” for your folder name, module name, and target name in Xcode. That’s the tricky I am talking about. If your app name is “BetterExtension” and module name is “Better Extension”, that makes things much wired. I’ll leave this issue discussed in next chapter, just make “Extension” for our example.

Release the power of Xcode Extension

Actually, what you need to do is import cocoa framework to have almost fundamental APIs available, except UI related. Not to mention, you could have 3rd party logic library imported to handle source code text. On the other hand, as you could use much powerful tool to handle in extension, but there is no UI for interactive processing data such as fetching data from a manual typed URL. Xcode Extension was designed for a single task triggered by a menu item and handled only one source file which is currently active. But we do want more before task execution having more features like a configuration panel or code completion/suggestion from cloud API or even code generation. So far we make some assumptions for better Xcode Extensions and how we make proof of concept (POC) in the following tutorials:

  • interactive UI → containing app
  • communication between UI and Extension → POC confirmed, using XPC
  • open custom URL in Extension (for UI appearing)→ POC confirmed
  • retain data in Extension
  • retain data in XPC → POC confirmed
  • HTTP/HTTPS request in Extension, XPC → POC confirmed

That’s all I think good enough for making a better extension. I think the storage is not very important in Xcode Extension scenario, so just drop this discussion at this time. Let’s back to the starting guide.

Debug Xcode Extension

Everything goes fine so far, and Xcode provide the default setting to show your Xcode Extension in debug mode. Let’s run it and see what we expect for.

Run, Target, and Target Device

After selecting “Extension” target and pressing “Run” button, the application chooser window will show up. Of course, the extension is for Xcode, so we just choose Xcode to run it.

application chooser window

The other Xcode with gray out color instance will be started, and select any project to succeed. To see the extension in menu, first you need point to a swift file because of the extension is associated with source file(swift).

fist run, and no our extension under Edit menu

Something wrong! There should be an extension called “Extension” in “Editor” menu, but there is not! Don’t panic, we need to do a change in target setting.

Click on “BetterExtension” on project tree root, select “Extension” target, select “General” tab, and scroll to “Frameworks and Libraries” section. Change “Embed” with value “Embed without signing”. That’s it, and run again.

change XcodeKit embed to “embed without signing”

Now you see the “Extension” under Editor menu properly.

Modify “Extension”’s items and trigger processing

Remember that Xcode Extension is trigger by clicking “Editor”/”Your Extension”/”item” menu, and the extension processes only current active source file. When we first time create “Xcode Source Editor Extension”, Xcode crate a folder with your Extension name in your project containing 4 files - 2 source files and 2 definition files.

Entitlement is the definition for extension’s sandbox. You don’t have to deal with it.

Info.plist is the only static config setting for this module, define extension’s item list, its identifier, display name, and binding class for triggering process.

By default, SouceEditorCommand.swift is the trigger entry point for further processing with current active source file.

The only thing you can programmatically change Info.plist definition is items under your extension, and you can modify it inside SourceEditorExtension.swift.

OK, we have more clear picture now:

  • Extension name(Product name) — defined by creating target
  • Extension item list — defined in Info.plist or override by SourceEditorExtension.swift. Each item has 3 properties

XCSourceEditorCommandIdentifierKey

XCSourceEditorCommandNameKey

XCSourceEditorCommandClassNameKey

  • Item clicked trigger point — item identifier is defined in Info.plist or override by SourceEditorExtension.swift. Processing handler (SouceEditorCommand.swift by default) is defined in Info.plist, and the handler switch item’s identifier for different process.

Next, let’s make some changes and see how it works…

From Info.plist

plist content

Click the Info.plist inside Extension folder, you’ll see the above details. As we described before, without deeper customizing from xcproj setting. We only can change binding class and items definition, so do not change display name at this moment as red rectangle highlighted area.

  • EDIT: “NSExtension”/“NSExtensionAttributes”/“XCSourceEditorCommandDefinitions”/“item0”/“XCSourceEditorCommandName” value from “Source Editor Command” to “Insert Hello World
  • EDIT: change name of “SourceEditorCommand.swift” to “BetterCommand.swift”. Yes, you don’t have to to it. This is just a tutorial to tell you if you want to change it into a meaningful name. Despite of changing name directly, I would prefer using refactor Xcode provided to cover all related source files, even file name. But the Info.plist is not part of source file, so we need to manually edit it.
refactoring SourceEditorCommand.swift
  • EDIT: “NSExtension”/“NSExtensionAttributes”/“XCSourceEditorCommandDefinitions”/“item0”/”XCSourceEditorCommandClassName” value from “$(PRODUCT_MODULE_NAME).SourceEditorCommand” to “$(PRODUCT_MODULE_NAME).BetterCommand

This the binding class when this item clicked.

  • EDIT: “NSExtension”/“NSExtensionAttributes”/“XCSourceEditorCommandDefinitions”/“item0”/”XCSourceEditorCommandIdentifier” value from “$(PRODUCT_BUNDLE_IDENTIFIER).SourceEditorCommand” to “BetterHelloWorld

This is just a String for identifier, so change it that is easy to remember.

  • EDIT: BetterCommand.swift

Run it and click “Inert Hello World”

New extension appearance

and you will see the result

That is what we expecting for, the identifier is reference by invocation.commandIdentifier. So we can add new item to our extension.

The easier way to add a new item is to manipulate Info.plist source format

Open as Source Code

EDIT: Extension/Info.plist

to

This will add a new item name “Remark all print” with identifier “BetterRemarkPrint”

New Info.plist looking

Run it and click each item

New item looking

and you will see the result

Result of each item

Now we can do implementation for each item

EDIT: Extension/BetterCommand.swift

Run it and click each item, then resulting…

For more information about handling invocation, you could find other resource from internet. But one thing sure, the invocation buffer is binding to Xcode source file. I also see some API like onDidChenge. Is it imply we may send a delegate to observe Xcode when something changed? But in this way, we are not even necessary to click the menu item. I’ll check and POC it later on.

Installation, Distribution & Packaging

There are 3ways for your code distribution

  • app store
  • DMG package

There are some articles in the internet for packaging DMG file. There is also a tool called “create-dmg”, just simply brew install create-dmg

  • source code compilation
  1. menu/product/archive the project
  2. The organizer will show up, then right click to select “show in finder”.

Then show content of the archived file

3. Drag Production/Application/BetterExtension to your system Application folder

Final Words

Structure of Xcode Extension app:

The containing app is the focus if we want our extension much powerful. I barely see extensions fully using it, but the containing app and extension are 2 standalone instances. We need some skills to let them co-work together.

Basic knowledge of Xcode Extension:

  • a sandboxed instance deal with current active source (one) file
  • only trigger by menu item clicking
  • No UI in extension, but not limits its logic capabilities

Hope this simple introduction of Xcode Extension has described clearly, and helpful for your creation. Thanks for your attending.

GitHub: kowei/BetterExtension: This repo is part of my tutorial in medium about Xcode Extension creation (github.com)

--

--