作者:裡脊串 授權本站轉載。
前言
上周聽了@makezl的插件開發直播介紹之後 萌生了寫個插件的想法 目的就是為了解決一直以來讓我很糾結的一個東西
Xcode的文件管理窗口的字體不等寬的問題
也就是這個東西
字體不等寬很難受有木有? 以前嘗試過用TinkerTool 但是問題多多
趁著這周有時間 所以花了點時間做了個插件MMNavigatorFont來解決這個問題
插件效果大概是這個樣子
如何開發插件 這裡就不介紹了 喵神的入門文章已經很好了
下面介紹一下開發過程中遇到的幾個問題以及解決辦法
問題
問題1 如何找到需要修改的對象
如圖 很明顯我是要修改圖中每一個控件的字體 但是我如何找到它呢?
首先想到的是監控所有的NSNotification 找出需要的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(allNotitication:) name:nil object:nil];
但是找了半天 都沒找到 這時候我想 要是有一款跟Reveal功能類似 可以查看OS X上的App結構的工具就好了 不過沒找到(有知道的朋友可以推薦一下)
這時我想起了chisel 除了調試iOS的應用 也可以調試OS X的應用 試了一下 果然可以
獲取pviews命令打印出來的結構文本 搜索對應的關鍵字 就能找到我所需的view class了 這裡我需要的就是這個DVTTableCellViewOneLine 再根據Xcode的頭文件就可以知道 DVTTableCellViewOneLine是基於DVTTableCellView的 而DVTTableCellView中有如下兩個成員
@interface DVTTableCellView : NSTableCellView { ... ... DVTTableCellViewTextField *_titleTextField; DVTTableCellViewTextField *_subtitleTextField; ... ... }
這就是我們需要修改字體的NSTextFiled
問題2 如何選擇字體
因為不熟悉Cocoa 所以我也在網上一番搜索以及請教了@劍指人心以後 得到了如下的代碼
- (void)actionChoose { [[NSFontManager sharedFontManager] setDelegate:self]; [[NSFontManager sharedFontManager] setTarget:self]; [[NSFontManager sharedFontManager] orderFrontFontPanel:nil]; } - (void)changeFont:(id)sender { self.selectedFont = [sender convertFont:self.selectedFont]; }
但是運行以後卻有問題 字體選擇框是彈出來了 但是始終獲取不到選擇的字體 而且changeFont中的sender(即[NSFontManager sharedFontManager])不為nil
經過一番嘗試之後發現 必須為NSFontManager指定一個初始字體才可以
[[NSFontManager sharedFontManager] setSelectedFont:self.selectedFont?:[NSFont systemFontOfSize:13] isMultiple:NO];
不過這裡我仍有一個疑問
在10.11中 NSFontManager的delegate已經被聲明為deprecated了 但是我查了官方文檔 也沒有找到替代的東西 是否有同學知道如何在10.11中正確的使用NSFontManager呢 :)
問題3 如何設置勾子
在cocoa中掛勾子肯定也是要用到Runtime的 這裡我學習BBUFullIssueNavigator直接使用Aspects來hook
Aspects使用起來很簡單 比如我在嘗試了幾次之後 發現在DVTTableCellViewOneLine的awakeFromNib方法執行之後對字體進行替換是最好的 那麼只需要這樣寫即可
[objc_getClass("DVTTableCellViewOneLine") aspect_hookSelector:@selector(awakeFromNib) withOptions:AspectPositionAfter usingBlock:fontBlock error:nil];
問題4 如何立即預覽修改字體的效果
因為鉤子是掛在awakeFromNib上的 所以當初始化完成之後 便無法再修改字體了 所以當字體發生變化的時候 需要遍歷所有的DVTTableCellViewOneLine並修改其中的字體
這倒不是難事 關鍵在於如何保存包含這些DVTTableCellViewOneLine的容器 不然每次都要遍歷整個Xcode的窗口 效率也未免太低了
經過對結構的觀察 發現IDENavigatorOutlineView是比較合適保存的 但是IDENavigatorOutlineView會因為切換而重新生成 不能保存強引用 所以這裡我定義了一個weak的NSView來保存它
@property (nonatomic, weak) NSView *outlineView;
並且在viewDidMoveToSuperview中hook住
[objc_getClass("IDENavigatorOutlineView") aspect_hookSelector:@selector(viewDidMoveToSuperview) withOptions:AspectPositionAfter usingBlock:controlBarBlock error:nil];
這樣 在修改了字體之後 只要遞歸遍歷其subviews並修改對應字體就好了
- (void)refreshFont { if ( self.outlineView ) { [self refreshFontInView:self.outlineView]; } } - (void)refreshFontInView:(NSView*)view { for ( NSView *v in view.subviews ) { [self refreshFontInView:v]; } if ( [view isKindOfClass:NSClassFromString(@"DVTTableCellViewOneLine")] ) { [self applyFont:view]; } }
問題5 如何還原默認字體
為了怕用戶不喜歡修改過的字體 所以我設置了一個啟用狀態 當用戶禁用的時候 會將所有字體還原成默認字體 這裡就需要記錄一下默認字體 很簡單 我用Category為NSView添加了兩個property
@interface NSView (MMNavigatorFont) @property (nonatomic, strong) NSFont *originalTitleFont; @property (nonatomic, strong) NSFont *originalSubtitleFont; @end
然後在的hook函數中記錄一下默認字體即可
NSView *view = info.instance; if ( !view.originalTitleFont ) { NSTextField *titleTextFiled = [view valueForKey:@"_titleTextField"]; NSTextField *subtitleTextFiled = [view valueForKey:@"_subtitleTextField"]; view.originalTitleFont = titleTextFiled.font; view.originalSubtitleFont = subtitleTextFiled.font; }
好了 至此一個功能完整的插件就完成了 如果你感興趣 趕緊用一下吧 現在用alcatraz可以搜索得到了
小結
一個功能簡單的Xcode插件就這麼誕生了 歷時一天半的樣子 雖然整個過程中也都是在摸石頭過河 不過因為cocoa和cocoa touch開發起來確實有很多相似的地方 所以開發起來也不是非常的困難(也是因為功能簡單的原因啦) 以後可能還會根據我自己的需求來開發更多的插件 XD
有同學說能不能改變Xcode的顏色 比如像我用的主題Monokai一樣弄個暗色的主題 其實你完全可以自己開發一個插件來做這個事情 說不定還會火哦~