转载

Xcode插件: MMNavigatorFont

前言

上周听了@makezl的插件开发直播介绍之后 萌生了写个插件的想法 目的就是为了解决一直以来让我很纠结的一个东西

Xcode的文件管理窗口的字体不等宽的问题

也就是这个东西

字体不等宽很难受有木有? 以前尝试过用 TinkerTool 但是问题多多

趁着这周有时间 所以花了点时间做了个插件 MMNavigatorFont 来解决这个问题

插件效果大概是这个样子

Xcode插件: MMNavigatorFont

如何开发插件 这里就不介绍了 喵神的 入门文章 已经很好了

下面介绍一下开发过程中遇到的几个问题以及解决办法

问题

问题1 如何找到需要修改的对象

Xcode插件: MMNavigatorFont 如图 很明显我是要修改图中每一个控件的字体 但是我如何找到它呢?

首先想到的是监控所有的NSNotification 找出需要的通知

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(allNotitication:)
name:nil
object:nil];

但是找了半天 都没找到 这时候我想 要是有一款跟Reveal功能类似 可以查看OS X上的App结构的工具就好了 不过没找到(有知道的朋友可以推荐一下)

这时我想起了 chisel 除了调试iOS的应用 也可以调试OS X的应用 试了一下 果然可以

Xcode插件: MMNavigatorFont

获取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呢 :)

Xcode插件: MMNavigatorFont

问题3 如何设置勾子

在cocoa中挂勾子肯定也是要用到Runtime的 这里我学习 BBUFullIssueNavigator 直接使用 Aspects 来hook

Aspects使用起来很简单 比如我在尝试了几次之后 发现在 DVTTableCellViewOneLineawakeFromNib 方法执行之后对字体进行替换是最好的 那么只需要这样写即可

[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插件: MMNavigatorFont

小结

一个功能简单的Xcode插件就这么诞生了 历时一天半的样子 虽然整个过程中也都是在摸石头过河 不过因为cocoa和cocoa touch开发起来确实有很多相似的地方 所以开发起来也不是非常的困难(也是因为功能简单的原因啦) 以后可能还会根据我自己的需求来开发更多的插件 XD

有同学说能不能改变Xcode的颜色 比如像我用的主题 Monokai 一样弄个暗色的主题 其实你完全可以自己开发一个插件来做这个事情 说不定还会火哦~

正文到此结束
Loading...