Xcode的文件管理窗口的字体不等宽的问题
也就是这个东西
字体不等宽很难受有木有? 以前尝试过用TinkerTool 但是问题多多
趁着这周有时间 所以花了点时间做了个插件MMNavigatorFont来解决这个问题
插件效果大概是这个样子
如何开发插件 这里就不介绍了 喵神的入门文章已经很好了
下面介绍一下开发过程中遇到的几个问题以及解决办法
问题
问题1 如何找到需要修改的对象
如图 很明显我是要修改图中每一个控件的字体 但是我如何找到它呢?
首先想到的是监控所有的NSNotification 找出需要的通知
1
2
3
4
|
[[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中有如下两个成员
1
2
3
4
5
6
7
8
9
|
@interface DVTTableCellView : NSTableCellView
{
...
...
DVTTableCellViewTextField *_titleTextField;
DVTTableCellViewTextField *_subtitleTextField;
...
...
}
|
这就是我们需要修改字体的NSTextFiled
问题2 如何选择字体
因为不熟悉Cocoa 所以我也在网上一番搜索以及请教了@剑指人心以后 得到了如下的代码
1
2
3
4
5
6
7
8
9
10
|
- (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指定一个初始字体才可以
1
|
[[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方法执行之后对字体进行替换是最好的 那么只需要这样写即可
1
2
3
4
|
[objc_getClass(
"DVTTableCellViewOneLine"
) aspect_hookSelector:@selector(awakeFromNib)
withOptions:AspectPositionAfter
usingBlock:fontBlock
error:nil];
|
问题4 如何立即预览修改字体的效果
因为钩子是挂在awakeFromNib上的 所以当初始化完成之后 便无法再修改字体了 所以当字体发生变化的时候 需要遍历所有的DVTTableCellViewOneLine并修改其中的字体
这倒不是难事 关键在于如何保存包含这些DVTTableCellViewOneLine的容器 不然每次都要遍历整个Xcode的窗口 效率也未免太低了
经过对结构的观察 发现IDENavigatorOutlineView是比较合适保存的 但是IDENavigatorOutlineView会因为切换而重新生成 不能保存强引用 所以这里我定义了一个weak的NSView来保存它
1
|
@property (nonatomic, weak) NSView *outlineView;
|
并且在viewDidMoveToSuperview中hook住
1
2
3
4
|
[objc_getClass(
"IDENavigatorOutlineView"
) aspect_hookSelector:@selector(viewDidMoveToSuperview)
withOptions:AspectPositionAfter
usingBlock:controlBarBlock
error:nil];
|
这样 在修改了字体之后 只要递归遍历其subviews并修改对应字体就好了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
- (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
1
2
3
4
|
@interface NSView (MMNavigatorFont)
@property (nonatomic, strong) NSFont *originalTitleFont;
@property (nonatomic, strong) NSFont *originalSubtitleFont;
@end
|
然后在的hook函数中记录一下默认字体即可
1
2
3
4
5
6
7
8
9
|
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一样弄个暗色的主题 其实你完全可以自己开发一个插件来做这个事情 说不定还会火哦~