PDA

View Full Version : Writing Plugins for Xcode 4




ArtOfWarfare
May 6, 2013, 06:37 PM
Hi guys,

I'm not asking for help with this (yet) but I thought I'd just share this article with anyone who might be interested in doing anything similar:

http://blacksmithsoftware.com/blog/2012/11/19/writing-your-own-xcode4-plugins



PatrickCocoa
May 6, 2013, 09:11 PM
Looks cool. Once you get started, can you extend KSImageNamed to show (in addition to the autocompleted list of names of .jpg and .png) the image size?

ArtOfWarfare
May 6, 2013, 09:29 PM
Looks cool. Once you get started, can you extend KSImageNamed to show (in addition to the autocompleted list of names of .jpg and .png) the image size?

I'm not sure what you're talking about, but based on what I've seen so far for Xcode 4 Plugins, I'd imagine so.

ArtOfWarfare
May 7, 2013, 03:16 PM
These were a helpful few lines to expedite debugging (add in a new Build Phrase -> Run Script at the end of your Build Phases for your target):

killall -9 Xcode
sleep 0.3
rm -fR ~/Library/Developer/Xcode/DerivedData
open /Applications/Utilities/Console.app
open /Applications/Xcode.app

This will restart Xcode so it will load the plugin as soon as it has finished building it and it'll also bring up Console so that you can view any NSLog statements you're using to debug.

Alternatively, you could do:

open -n /Applications/Xcode.app

Which will launch a second instance of Xcode which will have your new plugin while leaving the original instance alone. I didn't experiment with this much because my computer only has 2 GB of RAM so was going really slowly with two instances of Xcode running.

If you wanted to pursue that route, you should possibly do something involving the command GDB so that debugging features besides NSLog would work... I didn't get that far because I just don't have the memory to throw around experimenting with that.

Edit: I have found that running this code after Xcode finishes initializing is helpful with figuring out how to address different UIElements:

NSArray *allWindows = [NSApp windows];
[allWindows enumerateObjectsUsingBlock:^(NSWindow *window, NSUInteger idx, BOOL *stop) {
if (window.isVisible) {
[self recursivlyPrintSubviews:window.contentView
superOrigin:window.frame.origin
padding:[NSString stringWithFormat:@"%ld-", (unsigned long)idx]];
} else {
NSLog(@"%ld-Hidden Window", (unsigned long)idx);
}
}];

- (void)recursivlyPrintSubviews:(NSView *)view superOrigin:(CGPoint)superOrigin padding:(NSString *)pad
{
if (!NSEqualRects(view.visibleRect, NSZeroRect)) {
NSString *string = @"";
if ([view respondsToSelector:@selector(title)]) {
string = [view title];
} else if ([view respondsToSelector:@selector(stringValue)]) {
string = [view stringValue];
}
CGFloat originX = superOrigin.x + view.frame.origin.x;
CGFloat originY = superOrigin.y + view.frame.origin.y;
NSLog(@"%@%@: \"%@\" %@", pad, [view class], string, NSStringFromRect(CGRectMake(originX, originY, view.frame.size.width, view.frame.size.height)));
NSArray *allSubviews = view.subviews;
[allSubviews enumerateObjectsUsingBlock:^(NSView *subview, NSUInteger idx, BOOL *stop) {
[self recursivlyPrintSubviews:subview
superOrigin:CGPointMake(originX, originY)
padding:[NSString stringWithFormat:@"%@%ld-", pad, (unsigned long)idx]];
}];
} else {
NSLog(@"%@Fully Obscured View", pad);
}
}

In my case, this yielded the following output, which let me know that the sidebar visible on the left hand side of the main Xcode window is of the class IDENavigatorOutlineView - presumably a subclass of NSOutlineView, which means I can look into the methods that class provides to get an idea of how I can interact with Apple's private subclass (I'm finding this to be one of the most exciting programming projects I've ever worked on...)

0-Hidden Window
1-NSView: "" {{0, 352}, {1280, 352}}
1-0-DVTTabSwitcher: "" {{0, 352}, {1280, 350}}
1-0-0-NSTabView: "" {{0, 352}, {1280, 350}}
1-0-0-0-DVTControllerContentView: "" {{0, 352}, {1280, 350}}
1-0-0-0-0-DVTSplitView: "" {{0, 352}, {1280, 350}}
1-0-0-0-0-0-DVTReplacementView: "" {{0, 352}, {260, 350}}
1-0-0-0-0-0-0-DVTControllerContentView: "" {{0, 352}, {260, 350}}
1-0-0-0-0-0-0-0-NSView: "" {{0, 352}, {260, 350}}
1-0-0-0-0-0-0-0-0-DVTBorderedView: "" {{0, 374}, {260, 306}}
1-0-0-0-0-0-0-0-0-0-DVTReplacementView: "" {{0, 374}, {259, 306}}
1-0-0-0-0-0-0-0-0-0-0-DVTControllerContentView: "" {{0, 374}, {259, 306}}
1-0-0-0-0-0-0-0-0-0-0-0-NSScrollView: "" {{0, 374}, {259, 306}}
1-0-0-0-0-0-0-0-0-0-0-0-0-NSClipView: "" {{0, 374}, {259, 306}}
1-0-0-0-0-0-0-0-0-0-0-0-0-0-IDENavigatorOutlineView: "" {{0, 374}, {259, 306}}
1-0-0-0-0-0-0-0-0-0-0-0-1--Fully Obscured View
1-0-0-0-0-0-0-0-0-0-0-0-2--Fully Obscured View
1-0-0-0-0-0-0-0-1-DVTChooserView: "" {{0, 680}, {260, 22}}
1-0-0-0-0-0-0-0-1-0-NSMatrix: "1" {{18, 681}, {225, 21}}
1-0-0-0-0-0-0-0-2-DVTBorderedView: "" {{0, 352}, {260, 22}}
1-0-0-0-0-0-0-0-2-0-IDENavigatorFilterControlBar: "" {{0, 352}, {259, 22}}
1-0-0-0-0-0-0-0-2-0-0-DVTSearchField: "" {{89, 353}, {164, 19}}
1-0-0-0-0-0-0-0-2-0-1-DVTRolloverImageButton: "Button" {{30, 353}, {17, 21}}
1-0-0-0-0-0-0-0-2-0-2-DVTRolloverImageButton: "Button" {{49, 353}, {17, 21}}
1-0-0-0-0-0-0-0-2-0-3-DVTRolloverImageButton: "Button" {{68, 353}, {17, 21}}
1-0-0-0-0-0-0-0-2-0-4-IDEFilterBarGradientImagePopUpButton: "" {{0, 352}, {25, 21}}
1-0-0-0-0-1-DVTReplacementView: "" {{260, 352}, {760, 350}}
1-0-0-0-0-1-0-DVTControllerContentView: "" {{260, 352}, {760, 350}}
1-0-0-0-0-1-0-0-DVTSplitView: "" {{260, 352}, {760, 350}}
1-0-0-0-0-1-0-0-0-DVTLayoutView_ML: "" {{260, 352}, {760, 350}}
1-0-0-0-0-1-0-0-0-0--Fully Obscured View
1-0-0-0-0-1-0-0-0-1-NSView: "" {{260, 352}, {760, 350}}
1-0-0-0-0-1-0-0-0-1-0-DVTControllerContentView: "" {{260, 352}, {760, 350}}
1-0-0-0-0-1-0-0-0-1-0-0-DVTControllerContentView: "" {{260, 352}, {760, 350}}
1-0-0-0-0-1-0-0-0-1-0-0-0-NSView: "" {{260, 352}, {760, 350}}
1-0-0-0-0-1-0-0-0-1-0-0-0-0-IDENavBar: "" {{260, 680}, {760, 22}}
1-0-0-0-0-1-0-0-0-1-0-0-0-0-0-DVTBorderedView: "" {{341, 680}, {631, 22}}
1-0-0-0-0-1-0-0-0-1-0-0-0-0-0-0-IDEPathControl: "" {{341, 681}, {631, 21}}
1-0-0-0-0-1-0-0-0-1-0-0-0-0-1-IDEControlGroup: "" {{260, 680}, {81, 22}}
1-0-0-0-0-1-0-0-0-1-0-0-0-0-1-0-DVTBorderedView: "" {{260, 681}, {28, 21}}
1-0-0-0-0-1-0-0-0-1-0-0-0-0-1-0-0-DVTGradientImagePopUpButton: "" {{260, 681}, {28, 21}}
1-0-0-0-0-1-0-0-0-1-0-0-0-0-1-1-DVTBorderedView: "" {{288, 681}, {27, 21}}
1-0-0-0-0-1-0-0-0-1-0-0-0-0-1-1-0-DVTDelayedMenuGradientImageButton: "" {{288, 681}, {27, 21}}
1-0-0-0-0-1-0-0-0-1-0-0-0-0-1-2-DVTBorderedView: "" {{315, 681}, {26, 21}}
1-0-0-0-0-1-0-0-0-1-0-0-0-0-1-2-0-DVTDelayedMenuGradientImageButton: "" {{315, 681}, {26, 21}}
1-0-0-0-0-1-0-0-0-1-0-0-0-0-2-IDEControlGroup: "" {{972, 680}, {48, 22}}
1-0-0-0-0-1-0-0-0-1-0-0-0-0-2-0-DVTBorderedView: "" {{973, 681}, {47, 21}}
1-0-0-0-0-1-0-0-0-1-0-0-0-0-2-0-0-IDEEditorMenuStepperView: "" {{973, 681}, {47, 21}}
1-0-0-0-0-1-0-0-0-1-0-0-0-0-2-0-0-0-_IDEEditorStepperArrowButton: "" {{973, 681}, {15, 21}}
1-0-0-0-0-1-0-0-0-1-0-0-0-0-2-0-0-1-_IDEEditorStepperArrowButton: "" {{1005, 681}, {15, 21}}
1-0-0-0-0-1-0-0-0-1-0-0-0-0-2-0-0-2-IDEActionButton: "" {{988, 681}, {17, 21}}
1-0-0-0-0-1-0-0-0-1-0-0-0-1-DVTBorderedView: "" {{260, 352}, {760, 328}}
1-0-0-0-0-1-0-0-0-1-0-0-0-1-0-DVTControllerContentView: "" {{260, 352}, {760, 328}}
1-0-0-0-0-1-0-0-0-1-0-0-0-1-0-0-IDESourceCodeEditorContainerView: "" {{260, 352}, {760, 328}}
1-0-0-0-0-1-0-0-0-1-0-0-0-1-0-0-0-DVTSourceTextScrollView: "" {{260, 352}, {760, 328}}
1-0-0-0-0-1-0-0-0-1-0-0-0-1-0-0-0-0-NSClipView: "" {{295, 352}, {725, 328}}
1-0-0-0-0-1-0-0-0-1-0-0-0-1-0-0-0-0-0-DVTSourceTextView: "" {{295, 352}, {1104, 1513}}
1-0-0-0-0-1-0-0-0-1-0-0-0-1-0-0-0-1-NSScroller: "" {{295, 665}, {719, 15}}
1-0-0-0-0-1-0-0-0-1-0-0-0-1-0-0-0-2-DVTMarkedScroller: "" {{1005, 352}, {15, 322}}
1-0-0-0-0-1-0-0-0-1-0-0-0-1-0-0-0-3-DVTTextSidebarView: "" {{260, 352}, {35, 328}}
1-0-0-0-0-1-0-0-0-1-0-0-0-2--Fully Obscured View
1-0-0-0-0-1-0-0-1--Fully Obscured View
1-0-0-0-0-2-DVTSplitView: "" {{1020, 352}, {260, 350}}
1-0-0-0-0-2-0-DVTReplacementView: "" {{1020, 352}, {260, 328}}
1-0-0-0-0-2-0-0-DVTControllerContentView: "" {{1020, 352}, {260, 328}}
1-0-0-0-0-2-0-0-0-NSView: "" {{1020, 352}, {260, 328}}
1-0-0-0-0-2-0-0-0-0-DVTChooserView: "" {{1020, 658}, {260, 22}}
1-0-0-0-0-2-0-0-0-0-0-NSMatrix: "1" {{1118, 659}, {65, 21}}
1-0-0-0-0-2-0-0-0-1-DVTBorderedView: "" {{1020, 352}, {260, 306}}
1-0-0-0-0-2-0-0-0-1-0-NSScrollView: "" {{1021, 352}, {259, 306}}
1-0-0-0-0-2-0-0-0-1-0-0-NSClipView: "" {{1021, 352}, {259, 306}}
1-0-0-0-0-2-0-0-0-1-0-0-0-DVTStackView_ML: "" {{1021, 352}, {259, 100}}
1-0-0-0-0-2-0-0-0-1-0-0-0-0-DVTDisclosureView: "" {{1021, 352}, {259, 100}}
1-0-0-0-0-2-0-0-0-1-0-0-0-0-0-DVTDisclosureHeaderView: "Quick Help" {{1021, 352}, {259, 19}}
1-0-0-0-0-2-0-0-0-1-0-0-0-0-0-0-NSButton: "Button" {{1024, 355}, {13, 13}}
1-0-0-0-0-2-0-0-0-1-0-0-0-0-0-1-NSTextField: "Quick Help" {{1040, 355}, {237, 13}}
1-0-0-0-0-2-0-0-0-1-0-0-0-0-0-2--Fully Obscured View
1-0-0-0-0-2-0-0-0-1-0-0-0-0-0-3--Fully Obscured View
1-0-0-0-0-2-0-0-0-1-0-0-0-0-1-DVTControllerContentView: "" {{1021, 371}, {259, 80}}
1-0-0-0-0-2-0-0-0-1-0-0-0-0-1-0-WebView: "" {{1021, 371}, {259, 80}}
1-0-0-0-0-2-0-0-0-1-0-0-0-0-1-0-0-WebFrameView: "" {{1021, 371}, {259, 80}}
1-0-0-0-0-2-0-0-0-1-0-0-0-0-1-0-0-0-WebDynamicScrollBarsView: "" {{1021, 371}, {259, 80}}
1-0-0-0-0-2-0-0-0-1-0-0-0-0-1-0-0-0-0-WebClipView: "" {{1021, 371}, {259, 80}}
1-0-0-0-0-2-0-0-0-1-0-0-0-0-1-0-0-0-0-0-WebHTMLView: "" {{1021, 371}, {259, 80}}
1-0-0-0-0-2-0-0-0-1-0-0-0-0-1-0-0-0-1--Fully Obscured View
1-0-0-0-0-2-0-0-0-1-0-0-0-0-1-0-1-IDEQuickHelpPlaceholderView: "" {{1021, 371}, {259, 80}}
1-0-0-0-0-2-0-0-0-1-0-0-0-0-1-0-1-0-DVTLozengeTextField: "No Quick Help" {{1088, 399}, {124, 39}}
1-0-0-0-0-2-0-0-0-1-0-0-0-0-1-0-1-1-NSButton: "Search Documentation" {{1083, 383}, {135, 17}}
1-0-0-0-0-2-0-0-0-1-0-1--Fully Obscured View
1-0-0-0-0-2-0-0-0-1-0-2--Fully Obscured View
1-0-0-0-0-2-1-DVTReplacementView: "" {{1020, 680}, {260, 22}}
1-0-0-0-0-2-1-0-DVTControllerContentView: "" {{1020, 680}, {260, 22}}
1-0-0-0-0-2-1-0-0-NSView: "" {{1020, 491}, {260, 211}}
1-0-0-0-0-2-1-0-0-0-DVTChooserView: "" {{1020, 679}, {260, 23}}
1-0-0-0-0-2-1-0-0-0-0-NSMatrix: "1" {{1086, 680}, {129, 21}}
1-0-0-0-0-2-1-0-0-1--Fully Obscured View
1-1-DVTTabBarEnclosureView: "" {{0, 702}, {1280, 2}}
1-1-0--Fully Obscured View
2-Hidden Window

(Previously I had it printing out all views, but there was such a huge list of them that - though it was surprising they existed even though I was a few clicks away from being able to see them - weren't helpful.)

Edit 2X: I wrote a plugin that lets me browser MacRumors whenever I click on a framework on the navigation bar in Xcode... although I can manage posts, it seems that I can't upload attachments from this... I'll have to switch to Safari to upload a screenshot of it. In the screenshot you can see that the navigation bar above the editor doesn't change properly... further, the navigation bar doesn't allow you to select frameworks using it. Also, my code doesn't properly account for the possibility of multiple tabs, windows, or assistants right now, but still, I'm getting excited about the possibilities here...

ArtOfWarfare
May 11, 2013, 01:31 AM
I feel like tearing apart Xcode like this is teaching me a lot about the structure of OS X apps and GUIs...

Anyways, my prior print method wasn't actually telling me everything there was to know about the view hierarchy, because I was leaving off the toolbar. Toolbars belong directly to windows, and the items within them have labels. So I added this code in my window enumerator to print off a list of the toolbars:

NSLog(@"%ld-Toolbar Contains:", (unsigned long) idx);
[[window.toolbar visibleItems] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSLog(@" %@", [obj label]);
}];

This yielded:
Run
Stop
Scheme
Breakpoints

Editor
View
Organizer

Where the gap is an unlabeled toolbar item of the subclass _IDEActivityViewControllerToolbarItem

The fun really hasn't let up here in the week since I first started looking into making a plugin for Xcode... although I should knock it off with this project since there's other more important (IE, paid) work I should be doing...