美文网首页
创建无 Storyboard & XIB 的 macOS 应用

创建无 Storyboard & XIB 的 macOS 应用

作者: 跨端开发 | 来源:发表于2020-07-29 17:12 被阅读0次
    Mac OS
    前言

    学过 iOS 的都知道,一般不会使用 Main Storyboard 来创建企业级的 APP,而是在 Appdelegate 中手写实现创建 UIWindow 和设置 rootViewController,在切换到 MacOS 开发过程中,习惯性得以同样的方式创建 MacOS App,不过马上我就遇到了障碍。

    首先我把Info.plist中的Main interface配置项删除了,并且在applicationDidFinishLaunching中创建了自定义的NSWindow,并且按照Mac OS的方式设置为keyWindow并且显示,手起刀落直接Command+R,发现什么也没有发生,没有跑出任何界面,甚至连在Appdelegate中断点也没有跑。于是开始Google、百度。搜索到的
    大部分教程都只是阐明 NSWindowController、NSWindow、NSViewController 之前的关系,以及如何使用各个类。Mac OS的资料本来就少,而设置无Storyboard并且无XIB创建Mac OS的教程就更少了,不过最后还是找到了问题解决的答案。

    相比较iOS项目目录,Mac OS项目没有main.m的入口文件,而在Appdelegate中多了一个@NSApplicationMain的注解,默认Storyboard或XIB会关联设置Appdelegate,而如果删除则没有设置入口。导致APP Run起来以后,并没有调用APPdelegate。如果需要创建
    无 Storyboard&XIB 的 macOS 应用就需要手写Main入口。知道原因了那么我们就可以撸起袖子开干了。

    创建Mac OS应用

    打开Xcode->File -> New Project,选择APP。

    创建Mac OS应用

    输入项目名称MacOSAPP,Language选择Swift,User Innterface选择XIB(待会删除)

    创建Mac OS应用

    删除Main interface默认配置

    删除Info.plist配置项

    创建Main.swift

    在项目目录下创建一个main.swift文件,创建MainMenu和设置APPdelegate为入口。

    import Foundation
    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() //启动应用
    }
    
    

    如果不需要menu可以将改部分代码去除。

    配置NSWindowController、NSWindow、NSViewController

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

    打开AppDelegate.swift文件,在applicationDidFinishLaunching中设置自定义的NSWindow。代码大概如下:

    var mainWindowController: NSWindowController!
        
        lazy var window: NSWindow = {
            let w = NSWindow(contentRect: NSMakeRect(0, 0, 1300 , 520), styleMask: [.titled, .resizable, .miniaturizable, .closable, .fullSizeContentView], backing: .buffered, defer: false)
            w.center()
            w.backgroundColor = NSColor(calibratedRed: 0, green: 0, blue: 0, alpha: 1)
            w.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(CGWindowLevelKey.overlayWindow)))
            w.minSize = NSMakeSize(320, 240)
    
            return w
        }()
    
        func applicationDidFinishLaunching(_ aNotification: Notification) {
            // Insert code here to initialize your application
            mainWindowController = NSWindowController(window: window)
            mainWindowController.showWindow(nil)
            mainWindowController.window?.makeKeyAndOrderFront(nil)
            
            NSApplication.shared.mainWindow?.title = "Hello world"
            let scanViewCtrl = ScanViewController()
            window.contentViewController = scanViewCtrl
        }
    

    ScanViewController.siwft

    import Foundation
    
    class ScanViewController: NSViewController {
        lazy var label: NSTextField = {
            let v = NSTextField(labelWithString: "Press the button")
            v.translatesAutoresizingMaskIntoConstraints = false
    
            return v
        }()
    
    
        lazy var button: NSButton = {
            let v = NSButton(frame: .zero)
            v.translatesAutoresizingMaskIntoConstraints = false
    
            return v
        }()
    
        override func loadView() {
            // 设置 ViewController 大小同 mainWindow
            guard let windowRect = NSApplication.shared.mainWindow?.frame else { return }
            view = NSView(frame: windowRect)
        }
        
        override func viewDidLoad() {
            super.viewDidLoad()
            view.addSubview(label)
            view.addSubview(button)
    
            NSLayoutConstraint.activate([
                label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                label.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -20),
    
                button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                button.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 20),
                button.heightAnchor.constraint(equalToConstant: 30),
                button.widthAnchor.constraint(equalToConstant: 100)
                ])
    
            button.title = "Click me"
            button.target = self
            button.action = #selector(onClickme)
        }
        
        @objc func onClickme(_ sender: NSButton) {
            label.textColor = .red
            label.stringValue = "Yeah!"
        }
    }
    

    NSWindow、NSViewController、NSView之间的层级关系如下:

    +--------------------------------------------------------------+
    |                           NSWindow                           |
    |  +--------------------------------------------------------+  |
    |  |                    NSViewController                    |  |
    |  |  +--------------------------------------------------+  |  |
    |  |  |                      NSView                      |  |  |
    |  |  +--------------------------------------------------+  |  |
    |  +--------------------------------------------------------+  |
    +--------------------------------------------------------------+
    

    由于Mac OS是多窗口的,所以在Mac OS中还加入了NSWindowController用于管理Window,其关系可以类似iOS中UIViewController和UIView的关系。

    跑起来

    轻松的按下 Command+R,你的项目终于跑起来了。


    Run结果

    结束语

    由于需要手动设置Menu,其实建议还是建议Main interface使用XIB的方式,这种方式默认配置好了Menu和其他关联设置。也可以在APPdelegate中自定义设置NSWindow。减少了不必要的麻烦。本教程本着求真的态度分析和示例了如何使用纯代码创建MacOS应用。如果有更好的办法,欢迎大家在下面讨论交流。

    参考

    https://mikulove.com/2017/06/30/macos-xue-xi-bi-ji-shi-yong-chun-dai-ma-gou-jian-mac-ying-yong/

    相关文章

      网友评论

          本文标题:创建无 Storyboard & XIB 的 macOS 应用

          本文链接:https://www.haomeiwen.com/subject/ezhkrktx.html