NO.1 The Start

起初,由于拖延症,这篇文章我不想发的,但是纯代码构建项目的demo又写好了.前两天群里的朋友又问我要纯代码的demo.索性就借着机会把demo扔出来好了.给自己的懒癌加强一针.

NO.2 The Message

首先,从项目的结构开始看起.(因为懒,并没有做MVC分层,只是完成了基本的实现)

首先,最大的问题就是main.swift这个入口文件.默认的macOS工程是没有这个文件的.既然要用纯代码构建,那就把info.plist文件里面的指定Storyboard文件的部分删掉.这个跟iOS开发还是一模一样的.然后删掉Main.storyboard这个文件,反正没用了.删掉就是了.

然后,开始手写入口的main函数.在这里,你需要做两件事.第一,就是在实例化NSMenu对象.因为把storyboard删掉了,所以如果不实例化NSMenu的话,你得程序启动起来,左上角的菜单将空空如也.第二,就是手动实例化应用.因为是从头开始写,所以之前自动帮你生成的东西你也要自己写一遍(阿西巴,代码好多..当然了,快捷键也要自己绑定,这时候,你就可以随意开脑洞了,比如,⌘+Q改成新建……代码如下:

import Cocoa

func mainMenu() -> NSMenu {
    let    mainMenu             =    NSMenu()
    let    mainAppMenuItem      =    NSMenuItem(title: "Application", action: nil, keyEquivalent: "")
    let    mainFileMenuItem     =    NSMenuItem(title: "File", action: nil, keyEquivalent: "")
    mainMenu.addItem(mainAppMenuItem)
    mainMenu.addItem(mainFileMenuItem)
    
    let    appMenu              =    NSMenu()
    mainAppMenuItem.submenu     =    appMenu
    
    let    appServicesMenu      =    NSMenu()
    NSApp.servicesMenu          =    appServicesMenu
    appMenu.addItem(withTitle: "About", action: nil, keyEquivalent: "")
    appMenu.addItem(NSMenuItem.separator())
    appMenu.addItem(withTitle: "Preferences...", action: nil, keyEquivalent: ",")
    appMenu.addItem(NSMenuItem.separator())
    appMenu.addItem(withTitle: "Hide", action: #selector(NSApplication.hide(_:)), keyEquivalent: "h")
    appMenu.addItem({ ()->NSMenuItem in
        let m = NSMenuItem(title: "Hide Others", action: #selector(NSApplication.hideOtherApplications(_:)), keyEquivalent: "h")
        m.keyEquivalentModifierMask = NSEvent.ModifierFlags([.command, .option])
        return m
        }())
    appMenu.addItem(withTitle: "Show All", action: #selector(NSApplication.unhideAllApplications(_:)), keyEquivalent: "")
    
    appMenu.addItem(NSMenuItem.separator())
    appMenu.addItem(withTitle: "Services", action: nil, keyEquivalent: "").submenu    =    appServicesMenu
    appMenu.addItem(NSMenuItem.separator())
    appMenu.addItem(withTitle: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
    
    let    fileMenu             =    NSMenu(title: "File")
    mainFileMenuItem.submenu    =    fileMenu
    fileMenu.addItem(withTitle: "New...", action: #selector(NSDocumentController.newDocument(_:)), keyEquivalent: "n")
    
    return mainMenu
}

autoreleasepool {
    let app =   NSApplication.shared //创建应用
    let delegate = AppDelegate()
    app.delegate =  delegate //配置应用代理
    app.mainMenu = mainMenu() //配置菜单,mainMenu 函数需要前向定义,否则编译错误
    app.run() //启动应用
}

iOS的程序都是单窗口的,所以没有WindowController这个概念,而macOS不一样,如果是单窗口就坏事了.所以引入了WindowController这个概念,其实这个跟ViewViewController是差不多的.所以对Window的所有操作直接写在WindowController里面就好了.对View的操作写在ViewController里面.

由于是纯代码工程.所以我们需要手动创建自己的WindowControllerViewController.然后,在AppDelegate.swift里面对WindowController进行实例化.注意注释掉@NSApplicationMain.最后使用WindowControllershowWindow方法把这个窗口显示出来.

class AppDelegate: NSObject, NSApplicationDelegate {
    
    lazy var windowController: KZWindowController = {
        let windowController = KZWindowController()
        return windowController
    }()
    
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Insert code here to initialize your application
        self.windowController.showWindow(self)
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }


}

然后,在WindowController里面初始化Window.定义Window的大小,座标以及各种属性.然后重写init方法和init?(coder:)方法.最后,在init方法里面实例化ViewController.

class KZWindowController: NSWindowController {

    override func windowDidLoad() {
        super.windowDidLoad()
        
    }
    
    lazy var myWindow: NSWindow? = {
        let frame: CGRect = CGRect(x: 0, y: 0, width: 400, height: 280)
        let style: NSWindow.StyleMask = [.titled,.closable,.resizable]
        let back: NSWindow.BackingStoreType = .buffered
        let window: KZWindow = KZWindow(contentRect: frame, styleMask: style, backing: back, defer: false)
        window.title = "Test Window"
        window.windowController = self
        return window
}()
    
    lazy var viewController: KZViewController = {
        let viewController = KZViewController()
        return viewController
    }()
    
    override init(window: NSWindow?) {
        super.init(window: window)
        self.window = self.myWindow
        self.contentViewController = self.viewController
        self.window?.center()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

剩下的在ViewController里面实例化View就跟iOS差不了太多了.

class KZViewController: NSViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do view setup here.
    }
    
    lazy var myView: KZView = {
        let frame: CGRect = CGRect(x: 0, y: 0, width: 400, height: 300)
        let view: KZView = KZView(frame: frame)
        return view
    }()
    
    override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        self.view = self.myView
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}

最后,⌘+R运行一下,只要没有语法错误,这时候应用就已经启动了.然后你就会发现,WTF!!!写了这么多,就一个白板…..就是下面这个.

啥都没有,干净的像是白瓷砖….所以说,没有什么特殊的需求,不要使用纯代码….第一是累,第二嘛,就是麻烦.很多东西明明自动处理好了,但是还是要自己手写.比如上面的NSMenu.如果真的要使用代码的话,建议保留NSMenu相关的视图可视化部分.能省很多的功夫,少写很多不必要的代码.

NO.3 The Code

https://github.com/KarlZeo/macOSNoSBXib

NO.4 Thanks for

剑指人心大佬的入门书籍以及Demo.

NO.5 ChangeLog

2018.03.26 代码升级Swift 4,替换部分图片为代码块.

2017.06.30 发布,代码基于Swift 3.1.