Xcode is powerful, but some are closed, and the official does not provide documentation for Xcode plugin development. Meow-God's tutorial is more complete, but also suitable for getting started. The tutorial in this article is just as a summary of my development FKConsole
process and will not be comprehensive.
FKConsole
I developed a plugin for displaying Chinese in the Xcode console, very small, very simple. The plug-in was originally developed because a friend had this need, and did not find the appropriate plugin. If you do not use a plugin, you will need to embed the file in the project, he is not happy. So FKConsole
in the design will only modify the Xcode console text display, will never go to modify your files, this point you can rest assured.
Template
Because now there are a lot of people do Xcode plug-in development, so plug-in template This kind of thing also came into being.
Xcode-plugin-template is a basic template for Xcode plug-in development and can be installed directly with Alcatraz to support Xcode 6+.
After the installation is complete, a Xcode plugin option will appear when the project is created, which is the plugin project template in Xcode.
The template will generate NSObject_Extension
the same two files as your project name (. M).
NSObject_Extension.m
The +(无效)pluginDidLoad:(*一个NSBundle)插件
method is also the entrance of the entire plug-in.
In general, we want our plug-ins to survive the entire Xcode lifecycle, so it's generally a singleton, which is reflected in another file.
Add button
This blog post is a record FKConsole
of the development process, naturally with this example.
After Xcode starts, the notification will be sent NSApplicationDidFinishLaunchingNotification
, the template has been listened to, we have to add an option on the Header toolbar after the program starts to set the plug-in FKConsole
FKConsole
switch.
There are some differences between Mac software development and iOS development, and it uses the AppKit
UI library instead UIKit
, so it may feel a bit awkward.
NSApp表示
In the [NSApp表示mainMenu]
method can get to the head of the main button, which will contain very much NSMenuItem
, we will be in the Xcode Window
option before inserting an Plugins
option (refer to the practice of breaking the blog), and then add an option in this option FKConsole
. (Adding an Plugins
option is because some plug-ins will be added to, Edit
some will be added to, View
I have Window
not found the option for half a day, it is better to directly build an Plugins
option, the user can know at a glance where the plug-in. )
Nsmenu * MAINMENU = [Nsapp means MAINMENU]; if (! MAINMENU) {return;} Nsmenuitem * Pluginsmenuitem = [MAINMENU itemwithtitle:@"Plugin"]; if (! Pluginsmenuitem) {pluginsmenuitem = [[Nsmenuitem alloc] initialization]; Pluginsmenuitem. title Pseudo = @Plug-in; Pluginsmenuitem. submenu = [[Nsmenu page Header] Initwithtitle:pluginsmenuitem. Title Pseudo]; Nsinteger's Windowindex = [MAINMENU indexofitemwithtitle:@ "window"] Submenuitem .title pseudo = @ SubMenuItem .action = @selector (Togglemenu:) .boolvalue nsonstate:nsoffstate ; [Pluginsmenuitem .submenu Additem:submenuitem]
We need a state to represent the plug-in switch, just NSMenuItem
on one state
can represent the state, and just display the effect is good, we use it.
Layer
When the button is added, we now need to get an instance of the console. Unfortunately, Apple didn't give the document.
I'm sorry, I didn't find a layer view tool similar to reveal on Mac software development. Meow recommended one, the NSView
Dumping类
code is as follows:
From the http://onevcat.com/2013/02/xcode-plugin/.
- (Invalid) Dumpwithindent:(NSString *) Indent {The NSString * class = Nsstringfromclass ([Individual business category]);NSString * Information = @“” ;if ([From Respondstoselector:@selector (title)]) {The NSString * title = [From Performselector:@selector (title)];if (title =!0 && [title length]>0) {Information = [information stringbyappendingformat:@"Title =%@", Title]}}if ([From Respondstoselector:@selector (StringValue)]) {The NSString * string = [From Performselector:@selector (StringValue);if (string!) =0 && [string length]>0) {Information = [information stringbyappendingformat:@"StringValue =%@", string]; } }NSString * hint = [ self tool tip]; if (hint =! 0 && [hint length]> 0) {information = [information stringbyappendingformat:@] Span class= "hljs-string" > "hint =%@", prompt]. } NSLog (@ "%@%@%@", Indentation, class information); if ([ from child view] Count]> 0) { NSString * Subindent = [ NSString stringwithformat:@ "%@%@ ", indent, ([indent length]/2)%2 = = 0? @ "|" : @ ":"]; from Child view]) {[Child view dumpwithindent:subindent];}}
The effect is similar to the following:
In addition to this practice, I use chisel, which is a LLDB command-line auxiliary debugging tool for Facebook open source. It contains a pviews
command that can be directly recursive to print the entire key窗口
, the effect is as follows:
Import Private API
We found a class in it, IDEConsoleTextView
which is the only one in all the views seen in, and Console
we look at its frame and make sure the console is it.
Apple did not give this IDEConsoleTextView
to put AppKit
in, it is a private class, we now want to modify it, then we need to get its header file.
GitHub has a lot to dump out of Xcode in the header, you can see: Https://github.com/search?utf8=%E2%9C%93&q=xcode+header. We found it in the header IDEConsoleTextView.h
, in IDEKit
the middle.
As you can see in the header file, IDEConsoleTextView
it is inherited from DVTCompletingTextView
DVTTextView
NSTextView
. The content of the NSTextView
saved text is used NSTextStorage *textStorage
, so we want to modify IDEConsoleTextView
it textStorage
. But we NSTextStorage
did not find the specific text in the header file to save the properties, then we go to find.
Functional development
We loop through all the 的NSView
, find IDEConsoleTextView
, we look at its information:
We did not find its textStorage
properties and we tried to hit it in the console:
It has this attribute, just not seen in the debug area.
textStorage
There are two methods in the representation, namely:
//Send-process insideBefore you edit a fixed property.Delegates can change text or attributes.-(invalid) Textstorage:(Nstextstorage *) Textstorage willprocessediting:(nstextstorageeditactions) Editedmask Range:(nsrange) editedrange changeinlength: ( nsinteger) increment ns_available (10 _ 11,7 _0);//send correct-processediting notification layout Manager before, delegates can change properties. - (invalid) Textstorage: (nstextstorage *) textstorage didprocessediting: ( nstextstorageeditactions) Editedmask range: (nsrange" Editedrange changeinlength: ( Nsinteger) Increment ns_available (10 _11, 7 _0)
textStorage
After the character or description is modified, this proxy is triggered, so we implement this proxy method:
自.fkConsoleTextView .textStorage .delegate = 自我 ;- (无效)textStorage:(NSTextStorage *)textStorage willProcessEditing:(NSTextStorageEditActions)editedMask 范围:( NSRange)editedRange changeInLength :( NSInteger的)三角洲{}
OK, this time we found, IDEConsoleTextView
there is a _contents
property, this is an inherited from NSMutableAttributedString
the class, the inside of the mutableString
save text, mutableAttributes
Save the description of the text. This is the attribute we need to modify mutableString
.
We can get to the property using the proxy method, valueForKeyPath:
mutableString
So now we're going to convert it.
FKConsole
is used to adjust the display of the console in Chinese, in order to change the Unicode encoding similar to this () \U6d4b\U8bd5"
to "测试啊"
the normal display of ().
I'm in the calculator. Find a workaround code similar to this:
From http://stackoverflow.com/questions/13240620/uilabel-text-with-unicode-nsstring
- (的NSString *)stringByReplaceUnicode :( 的NSString *)的字符串{ 的NSMutableString * convertedString = [字符串mutableCopy] [convertedString replaceOccurrencesOfString:@ “\\ U” withString:@ “\\ U”选项:0范围:NSMakeRange(0,convertedString 。长度)]; CFStringRef变换= CFSTR( “ 任意六角/ Java”的); CFStringTransform((__桥CFMutableStringRef)convertedString,NULL,变换,YES); 返回 convertedString;}
We use 的setValue:forKeyPath:
the way to modify mutableString
properties.
Run, yes, but there are some problems.
- If you use Findview way to find
IDEConsoleTextView
, and then to set up the agent, then, when to go to Findview, if this time and new open a few pages, this is not sure.
- The modified text length is not the same as the original, even if the modification
editedRange
is not used. In this case, if you enter text or debug commands on the console, it may crash, the main reason for the crash is IDEConsoleTextView
_startLocationOfLastLine
to use and _lastRemovableTextLocation
these two properties to control the text start position and delete position, after the setting mutableString
, due to the length of the string value may occur out of bounds, and NSTextStorage
The agent is not getting hold of it IDEConsoleTextView
.
Monitoring notifications
For the first question, we can use the notification method to solve.
Refer to the cat's blog, you can listen to all the notifications, and then go to find which is what you need.
- (ID)的init { 如果(自 = [ 超级初始化]){ [ NSNotificationCenter defaultCenter]的addObserver:自我 选择:@选择(的NotificationListener :) 名称:无目标:零 ] } 回归 自我 ;} - (无效)的NotificationListener :( NSNotification *)的NotI { 的NSLog(@ “通知:%@”,[NotI位名]); }
We only need to listen NSTextDidChangeNotification
on the line, and then in the method to judge, then set up the agent.
- (无效)textStorageDidChange:(NSNotification *)的NotI{ 如果([NotI位。对象 isKindOfClass:NSClassFromString(@“IDEConsoleTextView”)&& ((IDEConsoleTextView *)的NotI。对象).textStorage。委派!=个体经营) { (。(IDEConsoleTextView *)的NotI 对象)。.textStorage 委托 =自我; }}
This solves the first problem.
Adding cross-blending of methods and means
If you are interested, you can refer to my other blog: objective-c runs common usage, which explains common run-time usage in an illustrative way.
For the second question, the approach I'm using is to modify IDEConsoleTextView
the _startLocationOfLastLine
attributes and properties at the right time _lastRemovableTextLocation
. After experimentation, the methods of collapse are mainly IDEConsoleTextView
these methods:
(void) insertText: (id)arg1; - (void) insertNewline: (id)arg1; - (void)clearConsoleItems; - ( BOOL ) shouldChangeTextInRanges: (id)arg1 replacementStrings: (id)arg2;
I IDEConsoleTextView
have added the following methods to the runtime:
(void) fk_insertText: (id)arg1; - (void) fk_insertNewline: (id)arg1; - (void)fk_clearConsoleItems; - ( BOOL ) fk_shouldChangeTextInRanges: (id)arg1 replacementStrings: (id)arg2;
After that, use Jrswizzle to swap, blending methods, similar to this:
-(invalid) Addmethodwithnewmethod:(SEL) Newmethod Originmethod:(SEL) originmethod{Method Targetmethod = Class_getinstancemethod(Nsclassfromstring (@"Ideconsoletextview"), Newmethod); Method Consolemethod = Class_getinstancemethod (console (! Target ( "Ideconsoletextview"), newmethod,consoleimp,method_gettypeencoding (Origin method" {Nserror * ERROR; [Nsclassfromstring (@ "Ideconsoletextview") jr_ Swizzlemethod:newmethod Withmethod:originmethod Error: Error] nslog (@ "error =%@", error);} }}
In the first fk_
series of methods, a pair of checks has been added IDEConsoleTextView
:
-(invalid) Fk_checktextview:(Ideconsoletextview *) If the textview{(TextView the. TextStore. Length <[textView valuesForkeypath: GStartlocationoflastlinekey] LongLongvalue]) {[setvalue:@ in TextView(Text view of the. Text store.) length) Forkeypath:kstartlocationoflastlinekey]; If (text is viewed by the. Text store. Length <[text view value Forkeypath: Gram Lastremovabletextlocationkey] Long Longvalue]) {[ TextView setvalue:@ (Text view of the. Text store.) length) Forkeypath:klastremovabletextlocationkey]; }}- (invalid) Fk_inserttext:(ID) arg1{[self-employed Fk_checktextview: (Ideconsoletextview *) self-employed]; [self-employed fk_inserttext:arg1];}
In this way, the second problem is solved.
OK, FKConsole
This is the basic development done.
Alcatraz Island
As mentioned above, Alcatraz
it is an open source Xcode package Manager. In fact, Alcatraz
it has become the most important tool for our current installation of the Xcode plugin.
Now we will FKConsole
submit it to the 恶魔
top.
Fill
Alcatraz-packages is a Alcatraz
list of package warehouses that packages.json
have all the Alcatraz
supported plugins, color themes, and templates saved.
We fork the alcatraz-packages into our code warehouse. After that, follow this format to add our project.
{ 名 ”:“FKConsole” , “ url可 ”:“https://github.com/Forkong/FKConsole” , “ 说明 ”:“FKConsole是一个插件的Xcode调整控制台显示器(对中国)。” , “ 屏幕 ”:“https://raw.githubusercontent.com/Forkong/FKConsole/master/Screenshots/demo.gif” }
Respec
rspec
It's a test framework written in Ruby, where the author writes a script to test if you've modified it to be packages.json
legitimate. Cut directly into the alcatraz-packages
directory and run the rspec
command. If you pass, you will see this:
RSpec的
Gemstones that use rubies can be mounted directly.
Pull
After verifying that there is no problem, our 拉入请求
submission appears on the Demon's package 拉请求
:
https://github.com/alcatraz/alcatraz-packages/pull/461
(You must not like me, did not see clear, directly added to the last side.) It is three categories, be sure to see clearly, to add to the category of plug-ins. )
http://blog.csdn.net/djl4104804/article/details/51075722
Xcode7 plug-in development: from Development to the Devil Island