变更记录

序号 录入时间 录入人 备注
1 2016-03-29 Alfred Jiang -
2 2016-03-31 Alfred Jiang -

方案名称

Xcode - 插件开发学习备忘

关键字

Xcode \ 插件开发 \ 开发插件

需求场景

  1. 开发自定义 Xcode 插件

参考链接

  1. OneV’s Den - Xcode 4 插件制作入门(推荐)
  2. CocoaChina - Xcode 6 插件开发入门:添加自己的想法和功能
  3. Forkong - Xcode7 插件开发:从开发到pull到Alcatraz
  4. ManiacDev - Xcode Plugin Guide – Find Xcode Plugins
  5. GitHub - Forkong/FKConsole
  6. GitHub - macoscope/CodePilot(推荐)
  7. GitHub - zulkis/ZKKeyBindingsTeacher

详细内容

1. 通过插件开发模板创建一个插件

(1) 安装 Alcatraz

curl -fsSL https://raw.githubusercontent.com/supermarin/Alcatraz/deploy/Scripts/install.sh | sh

(2) 重启 Xcode 选择 Load Bundle,通过 Window -> Package Manager 安装 Xcode Plugin

Image_00168_00001.png

Image_00168_00002.png

注:

  1. 如果无法通过 Alcatraz 安装 Xcode Plugin,可以在 GitHub 上直接下载 Xcode Plugin 编译运行,亦可自动安装插件开发模板
  2. 安装 Xcode Plugin 前可通过以下命令获取 Xcode UUID ,并在 info.plist -> DVTPlugInCompatibilityUUIDs 中添加该 UUID, 否则无法正常安装

    defaults read /Applications/Xcode.app/Contents/Info DVTPlugInCompatibilityUUID

Image_00168_00006.png

(3) 通过 File -> New -> Project -> Xcode Plugin 创建一个插件模板工程 TestXcodePlugin

Image_00168_00003.png

Image_00168_00004.png

Image_00168_00005.png

2. 为自定义插件添加快捷键支持

(1) 添加 IDEKeyBindingPreferenceSet.h, 该文件提供了 Xcode 快捷键绑定的私有接口
(2) 修改 TestXcodePlugin.m 文件如下
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
//
// TestXcodePlugin.m
// TestXcodePlugin
//
// Created by viktyz on 16/4/3.
// Copyright © 2016年 Alfred Jiang. All rights reserved.
//

#import "TestXcodePlugin.h"
#import "IDEKeyBindingPreferenceSet.h"


#define CP_DEFAULT_SHORTCUT @"$@X" // for key binding system
#define DEFAULTS_KEY_BINDING @"TestXcodePlugin.h"
#define CP_MENU_PARENT_TITLE @"Edit"
#define CP_MENU_ITEM_TITLE @"DoAction"


static NSString * const IDEKeyBindingSetDidActivateNotification = @"IDEKeyBindingSetDidActivateNotification";


@interface TestXcodePlugin()

@property (nonatomic, strong, readwrite) NSBundle *bundle;
@property (nonatomic, strong) NSMenuItem *actionMenuItem;

@end

@implementation TestXcodePlugin

+ (instancetype)sharedPlugin
{
return sharedPlugin;
}

- (id)initWithBundle:(NSBundle *)plugin
{
if (self = [super init]) {
// reference to plugin's bundle, for resource access
self.bundle = plugin;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didApplicationFinishLaunchingNotification:)
name:NSApplicationDidFinishLaunchingNotification
object:nil];
}
return self;
}

- (void)didApplicationFinishLaunchingNotification:(NSNotification*)noti
{
//removeObserver
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationDidFinishLaunchingNotification object:nil];

[self setupKeyBindingsIfNeeded];
[self installStandardKeyBinding];

// Create menu items, initialize UI, etc.
// Sample Menu Item:
NSMenuItem *menuItem = [[NSApp mainMenu] itemWithTitle:CP_MENU_PARENT_TITLE];
if (menuItem) {
[[menuItem submenu] addItem:[NSMenuItem separatorItem]];
self.actionMenuItem = [[NSMenuItem alloc] initWithTitle:CP_MENU_ITEM_TITLE action:@selector(doMenuAction) keyEquivalent:@""];
//[actionMenuItem setKeyEquivalentModifierMask:NSAlphaShiftKeyMask | NSControlKeyMask];
[self.actionMenuItem setTarget:self];
[[menuItem submenu] addItem:self.actionMenuItem];
[self updateMenuItem:self.actionMenuItem withShortcut:[self keyboardShortcutFromUserDefaults]];
}

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyBindingsHaveChanged:)
name:IDEKeyBindingSetDidActivateNotification
object:nil];
}

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

- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

#pragma mark -

- (id<IDEKeyboardShortcut>)keyboardShortcutFromUserDefaults
{
Class<IDEKeyboardShortcut> _IDEKeyboardShortcut = NSClassFromString(@"IDEKeyboardShortcut");
return [_IDEKeyboardShortcut keyboardShortcutFromStringRepresentation:[self keyBindingFromUserDefaults]];
}

- (void)setupKeyBindingsIfNeeded
{
if (IsEmpty([self keyBindingFromUserDefaults])) {
[self saveKeyBindingToUserDefaults:CP_DEFAULT_SHORTCUT forKey:DEFAULTS_KEY_BINDING];
}
}

- (NSString *)keyBindingFromUserDefaults
{
return [[NSUserDefaults standardUserDefaults] valueForKey:DEFAULTS_KEY_BINDING];
}

- (void)saveKeyBindingToUserDefaults:(NSString *)keyBinding forKey:(NSString *)defaultsKey
{
[[NSUserDefaults standardUserDefaults] setObject:keyBinding forKey:defaultsKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}

- (void)keyBindingsHaveChanged:(NSNotification *)notification
{
[self updateKeyBinding:[self currentUserCPKeyBinding] forMenuItem:self.actionMenuItem defaultsKey:DEFAULTS_KEY_BINDING];
}

- (void)updateKeyBinding:(id<IDEKeyBinding>)keyBinding forMenuItem:(NSMenuItem *)menuItem defaultsKey:(NSString *)defaultsKey
{
if ([[keyBinding keyboardShortcuts] count] > 0) {
id<IDEKeyboardShortcut> keyboardShortcut = [[keyBinding keyboardShortcuts] objectAtIndex:0];
[self saveKeyBindingToUserDefaults:[keyboardShortcut stringRepresentation] forKey:defaultsKey];
[self updateMenuItem:menuItem withShortcut:keyboardShortcut];
}
}

- (void)updateMenuItem:(NSMenuItem *)menuItem withShortcut:(id<IDEKeyboardShortcut>)keyboardShortcut
{
[menuItem setKeyEquivalent:[keyboardShortcut keyEquivalent]];
[menuItem setKeyEquivalentModifierMask:[keyboardShortcut modifierMask]];
}

- (id<IDEKeyBinding>)currentUserCPKeyBinding
{
return [self menuKeyBindingWithItemTitle:CP_MENU_ITEM_TITLE underMenuCalled:CP_MENU_ITEM_TITLE];
}

- (id<IDEMenuKeyBinding>)menuKeyBindingWithItemTitle:(NSString *)itemTitle underMenuCalled:(NSString *)menuName
{
Class<IDEKeyBindingPreferenceSet> _IDEKeyBindingPreferenceSet = NSClassFromString(@"IDEKeyBindingPreferenceSet");

id<IDEKeyBindingPreferenceSet> currentPreferenceSet = [[_IDEKeyBindingPreferenceSet preferenceSetsManager] currentPreferenceSet];

id<IDEMenuKeyBindingSet> menuKeyBindingSet = [currentPreferenceSet menuKeyBindingSet] ;

for (id<IDEMenuKeyBinding> keyBinding in [menuKeyBindingSet keyBindings]) {
if ([[keyBinding group] isEqualToString:menuName] && [[keyBinding title] isEqualToString:itemTitle]) {
return keyBinding;
}
}

return nil;
}

- (void)installStandardKeyBinding
{
Class<IDEKeyBindingPreferenceSet> _IDEKeyBindingPreferenceSet = NSClassFromString(@"IDEKeyBindingPreferenceSet");

id<IDEKeyBindingPreferenceSet> currentPreferenceSet = [[_IDEKeyBindingPreferenceSet preferenceSetsManager] currentPreferenceSet];

id<IDEMenuKeyBindingSet> menuKeyBindingSet = [currentPreferenceSet menuKeyBindingSet];

Class<IDEKeyboardShortcut> _IDEKeyboardShortcut = NSClassFromString(@"IDEKeyboardShortcut");

id<IDEKeyboardShortcut> defaultShortcut = [_IDEKeyboardShortcut keyboardShortcutFromStringRepresentation:[self keyBindingFromUserDefaults]];

Class<IDEMenuKeyBinding> _IDEMenuKeyBinding = NSClassFromString(@"IDEMenuKeyBinding");

id<IDEMenuKeyBinding> cpKeyBinding = [_IDEMenuKeyBinding keyBindingWithTitle:CP_MENU_ITEM_TITLE
parentTitle:CP_MENU_PARENT_TITLE
group:CP_MENU_ITEM_TITLE
actions:[NSArray arrayWithObject:@"whatever:"]
keyboardShortcuts:[NSArray arrayWithObject:defaultShortcut]];

[cpKeyBinding setCommandIdentifier:CP_MENU_ITEM_TITLE];

[menuKeyBindingSet insertObject:cpKeyBinding inKeyBindingsAtIndex:0];
[menuKeyBindingSet updateDictionary];
}

#pragma mark -

static inline BOOL IsEmpty(id thing) {
return thing == nil
|| ([NSNull null]==thing)
|| ([thing respondsToSelector:@selector(length)] && [(NSData *)thing length] == 0)
|| ([thing respondsToSelector:@selector(count)] && [(NSArray *)thing count] == 0);
}

@end
(3) 修改后,原插件操作按钮旁边会显示快捷键,使用快捷键可触发插件相应事件

Image_00168_00004.png

Image_00168_00005.png

效果图

(无)

备注

(无)