Как стать автором
Поиск
Написать публикацию
Обновить

Пишем свой Xcode plugin

Время на прочтение3 мин
Количество просмотров8.9K
Зачастую возникают ситуации, когда функционал используемой IDE хочется расширить. Везет, если разработчику предоставлены средства и документация для того, чтобы это сделать. К сожалению, в случае c Xcode это не так. Документирование возможностей остановилось на версии Xcode 3.0, так что никто не гарантирует, что в следующей версии написанный вами плагин заработает.

Примечание: за основу для написания данного топика был взят плагин ColorSense-for-Xcode.

Как я уже говорил, официально, Xcode не предоставляет публичного API для написания плагинов. При старте приложения, Xcode просматривает папку с плагинами (~/Library/Application Support/Developer/Shared/Xcode/Plug-ins) и загружает найденные (.xcplugin).

На самом деле, простенький плагин пишется за несколько часов, что вы и увидите далее.

Создаем новый Xcode проект

Плагин — всего навсего OS X бандл, создадим новый проект с типом 'bundle'.



При создании проекта, нужно убедиться, что ARC выключен, так как Xсode работает под управлением garbage collector, это же распространяется и на плагин.

Открываем таргет плагина и выставляем следующие настройки:

  • XC4Compatible = YES
  • XCPluginHasUI = NO
  • XCGCReady = YES
  • Principal class = {название главного класса плагина}




Конфигурируем Build Settings

Идем в build settings и выставляем следующие настройки:
  • Installation Build Products Location = ${HOME}
  • Installation Directory = /Library/Application Support/Developer/Shared/Xcode/Plug-ins
  • Deployment Location = YES
  • Wrapper extension = xcplugin


Также, нужно добавить несколько user-definded settings:
  • GCC_ENABLE_OBJC_GC = supported
  • GCC_MODEL_TUNING = G5


Мы указали, куда должен устанавливаться наш плагин после сборки. Важно: с отладкой плагинов все печально, и вам придется перезапускать Xcode после каждого билда.

Пишем плагин

Создадим новый класс и назовем его тем именем, что укзали в настройке Principal class. Когда Xcode загружает плагин, будет вызван метод + (void) pluginDidLoad: (NSBundle*) plugin, в котором можно произвести начальную настройку плагина (как правило, плагин — это синглтон).

+ (void) pluginDidLoad: (NSBundle*) plugin {
	static id sharedPlugin = nil;
	static dispatch_once_t once;
	dispatch_once(&once, ^{
		sharedPlugin = [[self alloc] init];
	});
}

- (id)init {
	if (self = [super init]) {
		[[NSNotificationCenter defaultCenter] addObserver:self 
                        selector:@selector(applicationDidFinishLaunching:) 
                            name:NSApplicationDidFinishLaunchingNotification 
                          object:nil];
	}
	return self;
}

в обработчике applicationDidFinishLaunching: мы можем непосредственно исполнять логику плагина. В нашем случае, мы подпишемся на нотификации изменения положения курсора в редакторе, а также добавим новый пункт в меню Edit.

- (void)applicationDidFinishLaunching:(NSNotification*)notification {
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(selectionDidChange:)
                                                 name:NSTextViewDidChangeSelectionNotification
                                               object:nil];
    
    
    NSMenuItem* editMenuItem = [[NSApp mainMenu] itemWithTitle:@"Edit"];
    if (editMenuItem) {
        [[editMenuItem submenu] addItem:[NSMenuItem separatorItem]];
        
        NSMenuItem* newMenuItem = [[NSMenuItem alloc] initWithTitle:@"Show autoresizing masks"
                                                             action:@selector(toggleMasks:)
                                                      keyEquivalent:@"m"];
        [newMenuItem setTarget:self];
        [newMenuItem setKeyEquivalentModifierMask:NSAlternateKeyMask];
        [[editMenuItem submenu] addItem:newMenuItem];
        [newMenuItem release];
    }
}

Для примера, будем выводить по изменению позиции курсора строку:

- (void)selectionDidChange:(NSNotification*)notification {
    if ([[notification object] isKindOfClass:[NSTextView class]]) {
        NSTextView* textView = (NSTextView *)[notification object];
        
        if (![[NSUserDefaults standardUserDefaults] boolForKey:kDLShowSizingsPreferencesKey]) {
            return;
        }
        
        NSArray* selectedRanges = [textView selectedRanges];
		if (selectedRanges.count >= 1) {
			NSRange selectedRange = [[selectedRanges objectAtIndex:0] rangeValue];
			NSString *text = textView.textStorage.string;
			NSRange lineRange = [text lineRangeForRange:selectedRange];
			NSString *line = [text substringWithRange:lineRange];
                 }
                        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
                        [alert setMessageText:line];
                        [alert runModal];
}

Простейший, ничего полезного не делающий плагин готов!

Замечания

Как я уже говорил выше, отладка плагина возможно только при помощи перезапуска Xcode (когда я писал плагин, я везде расставлял NSAlert и выводил нужную информацию). Из-за того, что документации как таковой нет, чтобы найти нужный вид в иерархии, или узнать какие нотификации отсылются, необходимо выполнить тот же самый трюк: вывести информацию либо в лог, либо в алерт. Если плагин падает, то Xcode не запустится, а плагин нужно удалить из ‘~/Library/Application Support/Developer/Shared/Xcode/Plug-ins’.

Более функциональный пример

Предпосылкой для написания статьи стал написанный мной небольшой плагин, который отображает маски авторазмера для UIView:



Исходник на github.

Спасибо за внимание!
Теги:
Хабы:
Всего голосов 30: ↑29 и ↓1+28
Комментарии10

Публикации

Ближайшие события