喵の窝

如何优雅地从iOS开发过度到macOS开发

一段并没有什么用的废话

在macOS中,界面库由UIKit变成了AppKit。因此,在头文件应用的时候需要引用对应的库。除此之外,在iOS中以UI开头的类也要替换成对应NS开头的类。比如NSApplication,NSWindow,NSView, NSImage, NSColor等等。而除了UIKit之外的framework则没有变化。CALayer,NSData,CGRect等等这些类型都可以直接使用的。

正片开始

试想下面这个场景:用word打开两个doc文件。这个场景下,内存的结构是什么样的呢?两个word的NSApplication,每个Application下有一个NSWindow?好吧,事实并不是如此。这个时候只有一个word的NSApplication实例,而这一个NSApplication下有两个NSWindow,每个NSWindow对应一个打开的文档。

NSApplication && NSApplicationDelegate

与iOS类似,NSApplication也有对应的Delegate。其中Delegate的职责与iOS中类似,负责处理Application的启动,关闭,打开,推送,点击docker图标的行为等等。具体的操作可以看NSApplicationDelegate protocol中申明的方法。
而NSApplication的职责则比(iOS中的)UIApplication多了窗口的管理和菜单栏的管理。因此,在NSApplication类中,可以看到

1
2
3
4
5
6
7
@property(readonly, weak) NSWindow *keyWindow;
@property(readonly, weak) NSWindow *mainWindow;

- (NSWindow *)windowWithWindowNumber:(NSInteger)windowNum;
@property(readonly, copy) NSArray<NSWindow *> *windows;

@property(strong) NSMenu *mainMenu;

这些与管理window和菜单键的相关的方法
mainMenu是Application的主菜单,就是显示在屏幕最上面菜单。
至于keyWindow和mainWindow的区别,文档上说mainWindow是当前的活动窗口,而keyWindow是获取当前键盘焦点的窗口,当然,这两个窗口可能都为nil。

NSMenu && NSMenuItem

NSMenu是菜单的数据结构,其中包含了菜单的标题(title)和菜单的内容。菜单的内容是一个NSMenuItem数组。一个NSMenuItem表示一个菜单项,每个菜单项中包含了菜单项的标题(title),触发函数以及快捷键。如果要添加子菜单,需要先在父菜单中添加一个NSMenuItem,再调用

1
2
- (void)setSubmenu:(NSMenu *)menu 
forItem:(NSMenuItem *)item;

给对应的NSMenuItem添加子菜单。
注:根据文档,如果一个子菜单是添加到mainMenu中,则mainMenu中显示的是子菜单的title,其它情况下则显示对应NSMenuItem的title

NSWindow

NSWindow是窗口的数据结构。除了显示内容外,NSWindow还用来控制窗口的行为。比如窗口是否能调整大小,窗口是否显示标题栏,是否显示工具栏,工具栏和标题栏是否合并(Xcode中的工具栏效果)等等。这些行为等可以通过NSWindow的

1
@property NSWindowStyleMask styleMask;

这个属性来控制。
而工具栏(NSToolbar)文档建议直接在storyboard里面拖就好了。没有什么可以定制化的。而对于置灰工具栏按钮的操作则有些蛋疼。有需要的话可以去查看API文档

NSStatusBar

NSStatusBar是全局状态栏,显示在全局菜单右边。状态栏并不是NSApplication或者NSWindow的一个属性,获取状态栏是通过NSStatusBar的class属性systemStatusBar来获取。

1
@property(class, readonly, strong) NSStatusBar *systemStatusBar;

状态栏的使用方法API文档已经说得非常清楚了。这里不再废话。

其它一些废话

虽然iOS中的大部分经验都能用在macOS中,比如如何给按钮添加对应的触发事件,又比如如何在storyboard中画界面。但是两套系统Framework的一些细小差别可能会让你在一些意想不到的地方阴沟里翻船。下面是我在开发一个简单的App时发现的两个坑点。

NSView

与UIView不一样,NSView默认情况下是不在CALayer上进行绘制的。所以默认情况下,NSView的layer属性返回的是nil。想要NSView使用CALayer进行绘制,需要将view的wantsLayer属性置为YES。

Storyboard

当在Storyboard中添加Table View,Text View这种自带滚动条的view时,系统会帮我们自动创建好Scroll View,然后将Table View或者Text View添加到自己的子View。我们可以在Scroll View中配置滚动条是否显示等滚动相关的属性,然后在子View中显示内容。