Xcodeプラグむン甚のプラグむン





「Xcodeプラグむンの䜜成」の公開に興味があるため、Xcodeの簡単なタむムトラッカヌを䜜成するこずにしたした。 私が経隓したプロセスは、この蚘事の本質です。 その䞭で、他のプラグむンをより速く、より効率的に曞くのに圹立぀いく぀かのプラグむンを分析したす。



むンタヌフェむスを備えたプラグむンの䞻なアむデアは、XI UIに統合され、可胜な限りネむティブに芋えるこずです。 しかし、Xcodeりィンドりを芋るずすぐに、「どのオブゞェクトがどこにあり、どのように必芁なオブゞェクトに統合できるかを理解する方法は」ずいう疑問がすぐに生じたす。 Xcodeにロヌドし、どのオブゞェクトがどこにあるかを䌝える簡単なプラグむンを䜜成したす。



最初のプラグむン



たず、 プラグむンのテンプレヌトをむンストヌルしお、プラグむンを䜜成したす。 次に、すべおが簡単です。Xcodeの構成を理解するには、そのオブゞェクトをログに出力する必芁がありたす。 これを行うには、ログをファむルに曞き蟌むか、ダむアログで衚瀺しお毎回閉じるこずができたす。 ああ、この情報をXcodeコン゜ヌルに盎接出力するのはどれほど䟿利でしょうか たあ、䜕も、2番目のプラグむンでこの問題を解決したすが、それに぀いおは少し埌で詳しく説明したす。 それたでの間、ログからオブゞェクトの堎所を凊理しないために、オブゞェクトの圱付き領域を含むXcodeりィンドりのスクリヌンショットを撮り、これをすべおファむルに保存したす。



最初のプラグむンは、すべおの「NSView」を通過し、それらを色付けしお、珟圚のりィンドりのスクリヌンショットを撮る1぀のメ゜ッドのみで構成されたす。 簡単に聞こえたすが、実際には1぀の小さなニュアンスがありたす。䞀郚のXView 'NSView'オブゞェクトは1぀の 'ContentView'以倖のものを持぀こずができず、独自のコンテンツビュヌを远加できたせん。 しかし、これは重芁ではありたせん。そのようなオブゞェクトを無芖しお、それらを掘り䞋げたす。



メ゜ッドテキスト
- (void)viewSnap:(NSView *)view { static int i = 0; // text field    NSTextField *name = [[NSTextField alloc] initWithFrame:view.bounds]; name.backgroundColor = [NSColor colorWithDeviceRed:rand()%255/255.f green:rand()%255/255.f blue:rand()%255/255.f alpha:0.3]; name.textColor = [NSColor blackColor]; NSString *string = view.className?:NSStringFromClass(view.class); name.stringValue = string?:@"unknown"; if (![view respondsToSelector:@selector(contentView)]) {//    text field? [view addSubview:name]; //      NSImage *captureImage = [[NSImage alloc] initWithData:[[NSApp keyWindow].contentView dataWithPDFInsideRect:[[NSApp keyWindow].contentView bounds]]]; [[captureImage TIFFRepresentation] writeToFile:[NSString stringWithFormat:@"%@%d.png", self.dirPath, i++] atomically:YES]; [name removeFromSuperview]; } for (NSView *v in view.subviews) { if ([v respondsToSelector:@selector(contentView)]) { NSView *vv = [v performSelector:@selector(contentView) withObject:nil]; [self viewSnap:vv]; } else { [self viewSnap:v]; } } }
      
      







そしお、メ゜ッド呌び出し

 - (void)doMenuAction { NSWindow * window = [NSApp keyWindow]; srand(time(NULL)); [self viewSnap:window.contentView]; }
      
      







その埌、写真を保存したフォルダを開いお楜しむこずができたす。 「NSView」のサむズに応じお、テキストのサむズを詊しおみる䟡倀がありたす。



結果は次のずおりです。









そしお、より矎しい圢で、手動で凊理した埌の結果は次のずおりです。



いく぀かの写真
画像

画像

画像

画像

画像



画像

画像

画像



すぐに2番目のプラグむンに移動したす。 Xcodeコン゜ヌルのプラグむンから情報を出力したす。



2番目のプラグむン



最初のプラグむンから、Xcodeのコン゜ヌルが「IDEConsoleTextView」クラスであるこずを孊びたした。 しかし、それはどのようなクラスで、どのメ゜ッドがありたすか 調べるには、いく぀かの方法がありたす。

1.りィンドりでコン゜ヌルを芋぀け、そのメ゜ッドをすべおファむルに衚瀺するプラグむンを䜜成したす

2. class-dumpを䜿甚しお、プラむベヌトフレヌムワヌクからすべおのヘッダヌを取埗し、そこでこのクラスを芋぀けようずしたす。

3. XVimプロゞェクトペヌゞに移動し、すべおのプラむベヌトリヌダヌをそこに連れお行きたす。



どの方向に行くかは絶察に関係ありたせん。䞻なこずは、コン゜ヌルが 'NSTextView'のサブクラスであり、次のメ゜ッドが含たれおいるこずを芋぀けるこずです insertText、 insertNewLine:。 これで、りィンドり内でコン゜ヌルを芋぀けお、そこに必芁な情報の行を曞き留めるこずができたした。



次に、ログモヌドを担圓するボタンを远加し、他のプラグむンから情報を取埗する必芁がありたす。



最初のプラグむンの埌、コン゜ヌルの隣にコントロヌルを含む「DVTScopeBarView」があるこずがわかりたす。 そこにボタンを眮きたす。 「DVTScopeBarView」ヘッダヌを芋お、クラスにaddViewOnRightメ゜ッドが含たれおいるこずを確認したす。 非垞に良いので、他の芁玠の䜍眮を気にせずに、ボタンをバヌに远加できたす。



IDEConsoleTextViewおよびDVTScopeBarViewを怜玢したす
 - (IDEConsoleTextView *)consoleViewInMainView:(NSView *)mainView { for (NSView *childView in mainView.subviews) { if ([childView isKindOfClass:NSClassFromString(@"IDEConsoleTextView")]) { return (IDEConsoleTextView *)childView; } else { NSView *v = [self consoleViewInMainView:childView]; if ([v isKindOfClass:NSClassFromString(@"IDEConsoleTextView")]) { return (IDEConsoleTextView *)v; } } } return nil; } - (DVTScopeBarView *)scopeBarViewInView:(NSView *)view { for (NSView *childView in view.subviews) { if ([childView isKindOfClass:NSClassFromString(@"DVTScopeBarView")]) { return (DVTScopeBarView *)childView; } else { NSView *v = [self scopeBarViewInView:childView]; if ([v isKindOfClass:NSClassFromString(@"DVTScopeBarView")]) { return (DVTScopeBarView *)v; } } } return nil; } - (void)someMethod { NSWindow *window = [NSApp keyWindow]; NSView *contentView = window.contentView; IDEConsoleTextView *console = [self consoleViewInMainView:contentView];//  DVTScopeBarView *scopeBar = nil; NSView *parent = console.superview; while (!scopeBar) { if (!parent) break; scopeBar = [self scopeBarViewInView:parent]; parent = parent.superview; } //...     }
      
      







これで、バヌにボタンが远加され、りィンドりにコン゜ヌルが芋぀かりたした。 どういうわけか他のプラグむンから情報を取埗しお衚瀺するこずが残っおいたす。 最も簡単なオプション「NSNotificationCenter」を䜿甚したす。 プラグむンはXcode環境にロヌドされ、そこから通知をキャッチできるため、プラグむン間でプラグむンを送信およびキャッチできたす。 必芁な通知をサブスクラむブし、コン゜ヌルにログを衚瀺するよう指瀺したす。 これを行うには、クラむアントファむル他のプラグむンが䜿甚するファむルに関数を䜜成し、必芁な通知を送信しおプラグむンでキャッチしたす。



ログ機胜ずコン゜ヌル衚瀺
 void PluginLogWithName(NSString *pluginName, NSString *format, ...) { NSString *name = @""; if (pluginName.length) { name = pluginName; } va_list argumentList; va_start(argumentList, format); NSString *string = [NSString stringWithFormat:@"%@ Plugin Console %@: ", [NSDate date], name]; NSString* msg = [[NSString alloc] initWithFormat:[NSString stringWithFormat:@"%@%@",string, format] arguments:argumentList]; NSMutableAttributedString *logString = [[NSMutableAttributedString alloc] initWithString:msg attributes:nil]; [logString setAttributes:[NSDictionary dictionaryWithObject:[NSFont fontWithName:@"Helvetica-Bold" size:15.f] forKey:NSFontAttributeName] range:NSMakeRange(0, string.length)]; [[NSNotificationCenter defaultCenter] postNotificationName:PluginLoggerShouldLogNotification object:logString]; va_end(argumentList); } - (void)addLog:(NSNotification *)notification {//  for (NSWindow *window in [NSApp windows]) {//     NSView *contentView = window.contentView; IDEConsoleTextView *console = [self consoleViewInMainView:contentView];//  console.logMode = 1;//     [console insertText:notification.object];//  [console insertNewline:@""];//     } }
      
      





ご芧のずおり、ログは絶察に任意のフォントで衚瀺できたす。





これで、2番目のプラグむンの準備ができたした。 完党な゜ヌスはここにありたす 。 プラグむンは次のようになりたす。







3番目のプラグむン



ええ、プラグむンが近くにあり、それらぞのアクセスがXcodeの巊パネルのセクションず同じであればいいでしょう...

プラグむンをXcodeりィンドりに統合するこずを考えずに誰でも独自のプラグむンを远加できるように、独自のパネルをXcodeに远加したしょう。



ここでは、以前の䞡方のプラグむンが圹立ちたす。 少なくずも圌らは私にずっお重宝したした。 ログを心配したり、無限のクラッシュを芋぀けたり、それらを理解したり、無限のヘッダヌファむルを掘り䞋げたりする必芁はありたせん。 結果に぀いおお話したす。



「NSToolbar」りィンドりがあり、ここにボタンを远加したす。 最も難しいのは、ツヌルバヌに芁玠を盎接远加するメ゜ッドがないこずです。 もちろん、私たちを「再定矩」する代理人はできたせん。 芁玠を远加するためのツヌルバヌを持぀唯䞀のメ゜ッド insertItemWithItemIdentifieratIndexが、芁玠自䜓がデリゲヌトを生成したす。 唯䞀の方法は、代理人が誰であるかを確認するこずです。 たぶんそれにいく぀かのアプロヌチがありたすか ログにデリゲヌトクラスを衚瀺し、「IDEToolbarDelegate」クラスを取埗したす。 さお、今床はclass-dumpで受け取った、たたはXVimから取埗したプラむベヌトヘッダヌに移動しお、そこでこのクラスを探したす。 このクラスには、関心のあるプロパティであるtoolbarItemProvidersずallowedItemIdentifiersがすぐに衚瀺されたす。 おそらく、デリゲヌトには、芁玠を提䟛するだけのオブゞェクトの蟞曞が含たれおいたす。 ログにtoolbarItemProvidersの珟圚の内容を衚瀺するず、次のような蟞曞が衚瀺されたす。



 { "some_id":<IDEToolbarItemProxy class>, "some_other_id":<IDEToolbarItemProxy class>, }
      
      





さお、もう1぀の手がかりがありたす-これはクラス 'IDEToolbarItemProxy'です。 たた、ヘッダヌのむンタヌフェむスを調べお、識別子「NSToolbar」の芁玠の識別子である可胜性が高いで初期化され、 providerClassプロパティがあるこずを確認したす。 しかし、 providerClassずは䜕で、どのように実装するのでしょうか 特定のクラスに含たれる内容を理解するには、2぀の方法がありたす。

1.これらのクラスずそのメ゜ッドを、蟞曞toolbarItemProvidersのすべおのプロバむダヌから掟生させたす。

2.空のクラスを䜜成し、蟞曞に远加し、Xcodeのクラッシュをキャッチしお、どのメ゜ッドが欠萜しおいるかを知らせたす。



私は2番目の方法を採甚したしたが、最初の方法はより正しいようです。 しかし、このプラグむンを曞いたずき、なんらかの理由でこの考えは思い぀きたせんでした。



したがっお、クラスを䜜成し、デリゲヌトに远加したす。



コヌド
 IDEToolbarDelegate *delegate = (IDEToolbarDelegate *)window.toolbar.delegate;//    if ([delegate isKindOfClass:NSClassFromString(@"IDEToolbarDelegate")]) { IDEToolbarItemProxy * proxy = [[NSClassFromString(@"IDEToolbarItemProxy") alloc] initWithItemIdentifier:PluginButtonIdentifier];//      proxy.providerClass = [PluginButtonProvider class];//    ( ) NSMutableDictionary *d = [NSMutableDictionary dictionaryWithDictionary:delegate.toolbarItemProviders];//    [d setObject:proxy forKey:proxy.toolbarItemIdentifier];//   delegate.toolbarItemProviders = d;//   NSMutableArray *ar = [NSMutableArray arrayWithArray:delegate.allowedItemIdentifiers];//     [ar addObject:proxy.toolbarItemIdentifier]; delegate.allowedItemIdentifiers = ar; [window.toolbar insertItemWithItemIdentifier:PluginButtonIdentifier atIndex:window.toolbar.items.count];//      }
      
      







プラグむンをむンストヌルし、Xcodeを再起動しお、すぐにクラッシュをキャッチしたす。 ログを芋お、クラスに+iditemForItemIdentifieridarg1 forToolbarInWindowidarg2メ゜ッドが必芁であるこずを理解したす。 このメ゜ッドは、プロトコル「IDEToolbarItemProvider」で説明されおいたす。 プラグむンをアンむンストヌルし、Xcodeを実行しおこのメ​​゜ッドを远加したす。 メ゜ッドの名前から、入力で識別子ずりィンドりを取埗し、出力でオブゞェクトを取埗する必芁があるこずは明らかです。 N回目のクラッシュずXcodeの再起動による詊行錯誀によるこのような操䜜により、これがクラス「DVTViewControllerToolbarItem」のオブゞェクトであるこずがわかりたす。 そしお、それはクラス「DVTGenericButtonViewController」で順番に初期化されたす。 オブゞェクト「DVTGenericButtonViewController」自䜓には、次の初期化がありたす。

Xcodeバヌゞョン6より前 initWithButtonactionBlockitemIdentifierwindow

バヌゞョン6以降 initWithButtonactionBlocksetupTeardownBlockitemIdentifierwindow

メ゜ッドの名前から、ボタン、抌されたずきに呌び出されるブロック、識別子、およびりィンドりが必芁であるこずは明らかです。



シンプルなボタンを䜜成し、必芁なコントロヌラヌを初期化したす。



コヌドドロップ
 DVTGenericButtonViewController *bvc = [(DVTGenericButtonViewController*)[NSClassFromString(@"DVTGenericButtonViewController") alloc] initWithButton:button actionBlock:^(NSButton *sender){} setupTeardownBlock:nil itemIdentifier:PluginButtonIdentifier window:arg2]; DVTViewControllerToolbarItem *c = [ NSClassFromString(@"DVTViewControllerToolbarItem") toolbarItemWithViewController:bvc];
      
      







プラグむンをむンストヌルし、Xcodeを再起動したす。 これでボタンがXcodeに远加されたした。 ボタンのハンドラヌを蚘述するこずは残っおいたす。 ボタンをクリックするず、開いおいない堎合は右偎のパネルが開き、オブゞェクトがこのパネルに远加されたす。 右偎のパネルを開き、最初のプラグむンを起動したす。 結果を確認するず、パネルが「DVTSplitView」オブゞェクトであるこずが明らかになりたす。 さらに、右偎のパネルが非衚瀺になっおいる堎合は、プログラムでそのパネルを開く方法を決定する必芁がありたす。 これを行うには、すべおの「NSToolbarItem」をりィンドりのツヌルバヌからログに出力したす。 必芁なオブゞェクトが最埌であるこずを知っおいたすボタンがただ远加されおいない堎合。 必芁な「NSToolbarItem」を取埗し、それを制埡するナヌザヌを確認したす。぀たり、「target」プロパティを確認したす。 「NSToolbarItem」のタヌゲットは、クラス「_IDEWorkspacePartsVisibilityToolbarViewController」のオブゞェクトです。 将来的にりィンドりで必芁な「NSToolbarItem」を芋぀けるためにのみ必芁なので、そのむンタヌフェむスを芋る必芁はありたせん突然それらは別の皮類に配眮されるか、誰かが私たちに芁玠を远加したす。 すべおの準備が敎ったので、右偎のパネルを衚瀺し、りィンドりでそれを芋぀けおオブゞェクトを远加できたす。



ボタン凊理
 NSWindow *window = arg2; NSToolbarItem *item = nil; for (NSToolbarItem *it in [[window toolbar] items]) {//     if ([it.target isMemberOfClass:NSClassFromString(@"_IDEWorkspacePartsVisibilityToolbarViewController")]) { item = it; break; } } NSSegmentedControl *control = (NSSegmentedControl *)item.view;//     if ([sender state] == NSOnState) {//   if (![control isSelectedForSegment:2]) {//    [control setSelected:YES forSegment:2];//     [item.target performSelector:item.action withObject:control];//   } DVTSplitView *splitView = [PluginButtonProvider splitViewForWindow:window];//   PanelView *myView = [[PluginPanel sharedPlugin] myViewForWindow:window];///     myView.frame = splitView.bounds;//  [myView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; for (NSView *sub in splitView.subviews) {//      [sub setHidden:YES]; } [splitView addSubview:myView];//   } else { DVTSplitView *splitView = [PluginButtonProvider splitViewForWindow:window];//  PanelView *myView = [[PluginPanel sharedPlugin] myViewForWindow:window];///   [myView removeFromSuperview];//     for (NSView *sub in splitView.subviews) {//      [sub setHidden:NO]; } }
      
      







私たちのオブゞェクトは「NSView」オブゞェクトであり、これには「DVTChooserView」ず通垞の「NSView」が含たれ、そこにプラグむンのコンテンツが远加されたす。 なぜ「DVTChooserView」なのですか パネルを可胜な限りXcodeりィンドりに合わせたい。 これを行うには、最初のプラグむンを起動し、巊偎のパネルを芋お、「DVTChooserView」が必芁なものであるこずがわかりたす。 「DVTChooserView」には、ボタンずボタンがオン/オフになったずきを刀断できる適切なデリゲヌトを備えた「NSMatrix」が含たれおいたす。 たた、このオブゞェクトは「DVTChoice」オブゞェクトを入力ずしお受け取り、操䜜したす。 「DVTChoice」にはアむコン、眲名、およびこのオブゞェクトを凊理するオブゞェクトが含たれおいるこずを考えるず、これは最も䟿利です。



オブゞェクトず远加芁玠
 //   DVTChooserView _chooserView = [[NSClassFromString(@"DVTChooserView") alloc] initWithFrame:NSZeroRect]; _chooserView.allowsEmptySelection = NO; _chooserView.allowsMultipleSelection = NO; _chooserView.delegate = self; //  - (void)chooserView:(DVTChooserView *)view userWillSelectChoices:(NSArray *)choices { DVTChoice *choice = [choices lastObject];//   self.contentView = [[choice representedObject] view];//   } // DVTChoice    DVTChoice *plugin = note.object;//    if (plugin) { NSWindow *window = [[note userInfo] objectForKey:PluginPanelWindowNotificationKey];//     PanelView *panel = [self myViewForWindow:window];//     [panel.chooserView.mutableChoices addObject:plugin];//   DVTChooserView if (!panel.contentView) { panel.contentView = [[[[panel.chooserView mutableChoices] lastObject] representedObject] view];//   ,    } }
      
      







以䞊です。 3番目のプラグむンの最も興味深い堎所を調べたした。 すべおの゜ヌスはこちらです。



プラグむンをパネルに远加したす



Xcodeにパネル党䜓を远加したした。 それでは、䜕かを埋めたしょう。



Xcodeの耇雑さを理解する必芁がなくなったため、わずか3行のコヌドでプラグむンをパネルに远加できたす。



3぀の魔法の線
 NSImage *image = [[NSImage alloc] initWithContentsOfFile:[[NSBundle bundleForClass:[self class]] pathForImageResource:@"plugin_icon"]];//     // ,    . 1-  TPViewController *c = [[TPViewController alloc] initWithNibName:@"TPView" bundle:[NSBundle bundleForClass:self.class]]; // DVTChoice    . 2-  DVTChoice *choice = [[NSClassFromString(@"DVTChoice") alloc] initWithTitle:@"Time" toolTip:@"Time management plugin" image:image representedObject:c]; //   ,        . 3-  PluginPanelAddPlugin(choice, [[note userInfo] objectForKey:PluginPanelWindowNotificationKey]);
      
      







これで、Xcodeりィンドりに独自のパネルが衚瀺され、プラグむンを远加できたす。 これで、プラグむンの䞀郚を1か所に配眮できたす。



最埌に-パネルの䜿甚䟋-Xcodeのシンプルなタむムトラッカヌ。



タむムプラグむン










All Articles