在没有 XCode 或 NIB 的情况下使用 Swift 在 OSX 上显示 window
Display window on OSX using Swift without XCode or NIB
免责声明:我正在尝试以下练习,因为我认为它会很有启发性。我对如何完成它很感兴趣。所以请不要急于加入 "This is the wrong way to do it, you should never do it like this!"
使用我最喜欢的文本编辑器从命令行工作,我想构建一个显示 window.
的最小 Swift 程序
这是一个 GUI/Cococa 你好世界,如果你愿意的话。
本着同样的精神,我想避免NIB。
所以,没有 XCode,没有 NIB。
我愿意:
- 用 swift 编译器编译它
- 创建#! swift 使用 Swift 解释器 运行 的脚本
如果我能做到这两点,我就会脚踏实地,升级到 Xcode。
我尝试了以下方法:
window.swift
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet weak var window: NSWindow!
let newWindow = NSWindow(contentRect : NSScreen.mainScreen()!.frame
, styleMask : NSBorderlessWindowMask
, backing : NSBackingStoreType.Buffered
, defer : false)
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
newWindow.opaque = false
newWindow.movableByWindowBackground = true
newWindow.backgroundColor = NSColor.whiteColor()
newWindow.makeKeyAndOrderFront(nil)
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
}
但是,尝试从命令行运行失败:
pi@piBookAir.local ~ /Users/pi/dev/macdev:
⤐ swift window.swift
window.swift:3:1: error: 'NSApplicationMain' attribute cannot be used in a
module that contains top-level code
@NSApplicationMain
^
window.swift:1:1: note: top-level code defined in this source file
import Cocoa
^
✘
消除错误的正确方法是什么?
将 -parse-as-library
标志传递给 swiftc
:
swiftc -parse-as-library window.swift
也就是说,当您这样做时,您最终会 运行 陷入第二个错误:
2015-06-10 14:17:47.093 window[11700:10854517] No Info.plist file in application bundle or no NSPrincipalClass in the Info.plist file, exiting
制作文件TestView.swift(像这样):
import AppKit
class TestView: NSView
{
override init(frame: NSRect)
{
super.init(frame: frame)
}
required init?(coder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
var colorgreen = NSColor.greenColor()
override func drawRect(rect: NSRect)
{
colorgreen.setFill()
NSRectFill(self.bounds)
let h = rect.height
let w = rect.width
let color:NSColor = NSColor.yellowColor()
let drect = NSRect(x: (w * 0.25),y: (h * 0.25),width: (w * 0.5),height: (h * 0.5))
let bpath:NSBezierPath = NSBezierPath(rect: drect)
color.set()
bpath.stroke()
NSLog("drawRect has updated the view")
}
}
制作文件TestApplicationController.swift(像这样):
import AppKit
final class TestApplicationController: NSObject, NSApplicationDelegate
{
/// Seems fine to create AppKit UI classes before `NSApplication` object
/// to be created starting OSX 10.10. (it was an error in OSX 10.9)
let window1 = NSWindow()
let view1 = TestView(frame: NSRect(x: 0, y: 0, width: 1000, height: 1000))
func applicationDidFinishLaunching(aNotification: NSNotification)
{
window1.setFrame(CGRect(x: 0, y: 0, width: 1000, height: 1000), display: true)
window1.contentView = view1
window1.opaque = false
window1.center();
window1.makeKeyAndOrderFront(self)
// window1.backgroundColor = view1.colorgreen
// window1.displayIfNeeded()
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
}
制作文件main.swift(像这样):
//
// main.swift
// CollectionView
//
// Created by Hoon H. on 2015/01/18.
// Copyright (c) 2015 Eonil. All rights reserved.
//
import AppKit
let app1 = NSApplication.sharedApplication()
let con1 = TestApplicationController()
app1.delegate = con1
app1.run()
最后一个文件不能重命名,main.swift显然是swift的特殊名称(否则无法编译示例)。
现在,输入这个(编译示例):
swiftc -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk TestView.swift TestApplicationController.swift main.swift
运行 输入代码:
./main
它显示了一个主 window(居中),其中有一个漂亮的绿色和一个黄色矩形)。
您可以通过输入 Control-C 来终止该应用程序。
请注意,这是一个 swift 编译而非解释器 运行,因此您有一个本机应用程序。
注意 -sdk 和 MacOSX10 的路径。11.sdk 是必不可少的(否则代码将无法编译)。
另请注意,此编译依赖于最新的 Xcode 发行版,因此请将 MacOSX10.11.sdk 更新为 MacOSX10.10.sdk 或 SDK 目录中的任何内容。
花了一段时间才找到这个...
将 this 代码从 objective-c 移植到 Swift,你会得到
import Cocoa
let nsapp = NSApplication.shared()
NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular)
let menubar = NSMenu()
let appMenuItem = NSMenuItem()
menubar.addItem(appMenuItem)
NSApp.mainMenu = menubar
let appMenu = NSMenu()
let appName = ProcessInfo.processInfo.processName
let quitTitle = "Quit " + appName
let quitMenuItem = NSMenuItem.init(title:quitTitle,
action:#selector(NSApplication.terminate),keyEquivalent:"q")
appMenu.addItem(quitMenuItem);
appMenuItem.submenu = appMenu;
let window = NSWindow.init(contentRect:NSMakeRect(0, 0, 200, 200),
styleMask:NSTitledWindowMask,backing:NSBackingStoreType.buffered,defer:false)
window.cascadeTopLeft(from:NSMakePoint(20,20))
window.title = appName;
window.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps:true)
NSApp.run()
另存为minimal.swift,编译
swiftc minimal.swift -o minimal
和运行
./minimal
您将得到一个空的 window 和一个菜单栏,其中的菜单名称与应用程序类似,还有一个退出按钮。
为什么它能正常工作,我不知道。我是 Swift 和 Cocoa 编程的新手,但链接的网站解释了一点。
基于其他解决方案,我编写了一个最新版本的单文件应用程序。
要求:Swift 5.6,命令行工具,没有 XCode,没有 XIB,没有 Storyboard。
一个文件,一个class,运行它与swift app.swift
。
文件:app.swift
import AppKit
class App : NSObject, NSApplicationDelegate {
let app = NSApplication.shared
let name = ProcessInfo.processInfo.processName
let status = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
let window = NSWindow.init(
contentRect: NSMakeRect(0, 0, 200, 200),
styleMask: [.titled, .closable, .miniaturizable],
backing: .buffered,
defer: false
)
override init() {
super.init()
app.setActivationPolicy(.accessory)
window.center()
window.title = name
window.hidesOnDeactivate = false
window.isReleasedWhenClosed = false
let statusMenu = newMenu()
status.button?.title = ""
status.menu = statusMenu
let appMenu = newMenu()
let sub = NSMenuItem()
sub.submenu = appMenu
app.mainMenu = NSMenu()
app.mainMenu?.addItem(sub)
}
@IBAction func activate(_ sender:Any?) {
app.setActivationPolicy(.regular)
DispatchQueue.main.async { self.window.orderFrontRegardless() }
}
@IBAction func deactivate(_ sender:Any?) {
app.setActivationPolicy(.accessory)
DispatchQueue.main.async { self.window.orderOut(self) }
}
private func newMenu(title: String = "Menu") -> NSMenu {
let menu = NSMenu(title: title)
let q = NSMenuItem.init(title: "Quit", action: #selector(app.terminate(_:)), keyEquivalent: "q")
let w = NSMenuItem.init(title: "Close", action: #selector(deactivate(_:)), keyEquivalent: "w")
let o = NSMenuItem.init(title: "Open", action: #selector(activate(_:)), keyEquivalent: "o")
for item in [o,w,q] { menu.addItem(item) }
return menu
}
func applicationDidFinishLaunching(_ n: Notification) { }
func applicationDidHide(_ n: Notification) {
app.setActivationPolicy(.accessory)
DispatchQueue.main.async { self.window.orderOut(self) }
}
}
let app = NSApplication.shared
let delegate = App()
app.delegate = delegate
app.run()
上面的 64 行代码提供了以下功能并修复了以前的解决方案:
- 应用程序菜单可见且可用
- 状态菜单可以点击使用
- 两个菜单都有可用的键绑定
- 打开 window 时出现停靠项目:
app.setActivationPolicy(.regular)
- 停靠项目在 window 关闭时隐藏:
app.setActivationPolicy(.accessory)
- window 在关闭时保留并在打开时重复使用:
window.hidesOnDeactivate = false
window.isReleasedWhenClosed = false
在 M1 Pro 上测试。这是它的样子。
免责声明:我正在尝试以下练习,因为我认为它会很有启发性。我对如何完成它很感兴趣。所以请不要急于加入 "This is the wrong way to do it, you should never do it like this!"
使用我最喜欢的文本编辑器从命令行工作,我想构建一个显示 window.
的最小 Swift 程序这是一个 GUI/Cococa 你好世界,如果你愿意的话。
本着同样的精神,我想避免NIB。
所以,没有 XCode,没有 NIB。
我愿意:
- 用 swift 编译器编译它
- 创建#! swift 使用 Swift 解释器 运行 的脚本
如果我能做到这两点,我就会脚踏实地,升级到 Xcode。
我尝试了以下方法:
window.swift
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet weak var window: NSWindow!
let newWindow = NSWindow(contentRect : NSScreen.mainScreen()!.frame
, styleMask : NSBorderlessWindowMask
, backing : NSBackingStoreType.Buffered
, defer : false)
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
newWindow.opaque = false
newWindow.movableByWindowBackground = true
newWindow.backgroundColor = NSColor.whiteColor()
newWindow.makeKeyAndOrderFront(nil)
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
}
但是,尝试从命令行运行失败:
pi@piBookAir.local ~ /Users/pi/dev/macdev:
⤐ swift window.swift
window.swift:3:1: error: 'NSApplicationMain' attribute cannot be used in a
module that contains top-level code
@NSApplicationMain
^
window.swift:1:1: note: top-level code defined in this source file
import Cocoa
^
✘
消除错误的正确方法是什么?
将 -parse-as-library
标志传递给 swiftc
:
swiftc -parse-as-library window.swift
也就是说,当您这样做时,您最终会 运行 陷入第二个错误:
2015-06-10 14:17:47.093 window[11700:10854517] No Info.plist file in application bundle or no NSPrincipalClass in the Info.plist file, exiting
制作文件TestView.swift(像这样):
import AppKit
class TestView: NSView
{
override init(frame: NSRect)
{
super.init(frame: frame)
}
required init?(coder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
var colorgreen = NSColor.greenColor()
override func drawRect(rect: NSRect)
{
colorgreen.setFill()
NSRectFill(self.bounds)
let h = rect.height
let w = rect.width
let color:NSColor = NSColor.yellowColor()
let drect = NSRect(x: (w * 0.25),y: (h * 0.25),width: (w * 0.5),height: (h * 0.5))
let bpath:NSBezierPath = NSBezierPath(rect: drect)
color.set()
bpath.stroke()
NSLog("drawRect has updated the view")
}
}
制作文件TestApplicationController.swift(像这样):
import AppKit
final class TestApplicationController: NSObject, NSApplicationDelegate
{
/// Seems fine to create AppKit UI classes before `NSApplication` object
/// to be created starting OSX 10.10. (it was an error in OSX 10.9)
let window1 = NSWindow()
let view1 = TestView(frame: NSRect(x: 0, y: 0, width: 1000, height: 1000))
func applicationDidFinishLaunching(aNotification: NSNotification)
{
window1.setFrame(CGRect(x: 0, y: 0, width: 1000, height: 1000), display: true)
window1.contentView = view1
window1.opaque = false
window1.center();
window1.makeKeyAndOrderFront(self)
// window1.backgroundColor = view1.colorgreen
// window1.displayIfNeeded()
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
}
制作文件main.swift(像这样):
//
// main.swift
// CollectionView
//
// Created by Hoon H. on 2015/01/18.
// Copyright (c) 2015 Eonil. All rights reserved.
//
import AppKit
let app1 = NSApplication.sharedApplication()
let con1 = TestApplicationController()
app1.delegate = con1
app1.run()
最后一个文件不能重命名,main.swift显然是swift的特殊名称(否则无法编译示例)。
现在,输入这个(编译示例):
swiftc -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk TestView.swift TestApplicationController.swift main.swift
运行 输入代码:
./main
它显示了一个主 window(居中),其中有一个漂亮的绿色和一个黄色矩形)。 您可以通过输入 Control-C 来终止该应用程序。
请注意,这是一个 swift 编译而非解释器 运行,因此您有一个本机应用程序。
注意 -sdk 和 MacOSX10 的路径。11.sdk 是必不可少的(否则代码将无法编译)。
另请注意,此编译依赖于最新的 Xcode 发行版,因此请将 MacOSX10.11.sdk 更新为 MacOSX10.10.sdk 或 SDK 目录中的任何内容。
花了一段时间才找到这个...
将 this 代码从 objective-c 移植到 Swift,你会得到
import Cocoa
let nsapp = NSApplication.shared()
NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular)
let menubar = NSMenu()
let appMenuItem = NSMenuItem()
menubar.addItem(appMenuItem)
NSApp.mainMenu = menubar
let appMenu = NSMenu()
let appName = ProcessInfo.processInfo.processName
let quitTitle = "Quit " + appName
let quitMenuItem = NSMenuItem.init(title:quitTitle,
action:#selector(NSApplication.terminate),keyEquivalent:"q")
appMenu.addItem(quitMenuItem);
appMenuItem.submenu = appMenu;
let window = NSWindow.init(contentRect:NSMakeRect(0, 0, 200, 200),
styleMask:NSTitledWindowMask,backing:NSBackingStoreType.buffered,defer:false)
window.cascadeTopLeft(from:NSMakePoint(20,20))
window.title = appName;
window.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps:true)
NSApp.run()
另存为minimal.swift,编译
swiftc minimal.swift -o minimal
和运行
./minimal
您将得到一个空的 window 和一个菜单栏,其中的菜单名称与应用程序类似,还有一个退出按钮。
为什么它能正常工作,我不知道。我是 Swift 和 Cocoa 编程的新手,但链接的网站解释了一点。
基于其他解决方案,我编写了一个最新版本的单文件应用程序。
要求:Swift 5.6,命令行工具,没有 XCode,没有 XIB,没有 Storyboard。
一个文件,一个class,运行它与swift app.swift
。
文件:app.swift
import AppKit
class App : NSObject, NSApplicationDelegate {
let app = NSApplication.shared
let name = ProcessInfo.processInfo.processName
let status = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
let window = NSWindow.init(
contentRect: NSMakeRect(0, 0, 200, 200),
styleMask: [.titled, .closable, .miniaturizable],
backing: .buffered,
defer: false
)
override init() {
super.init()
app.setActivationPolicy(.accessory)
window.center()
window.title = name
window.hidesOnDeactivate = false
window.isReleasedWhenClosed = false
let statusMenu = newMenu()
status.button?.title = ""
status.menu = statusMenu
let appMenu = newMenu()
let sub = NSMenuItem()
sub.submenu = appMenu
app.mainMenu = NSMenu()
app.mainMenu?.addItem(sub)
}
@IBAction func activate(_ sender:Any?) {
app.setActivationPolicy(.regular)
DispatchQueue.main.async { self.window.orderFrontRegardless() }
}
@IBAction func deactivate(_ sender:Any?) {
app.setActivationPolicy(.accessory)
DispatchQueue.main.async { self.window.orderOut(self) }
}
private func newMenu(title: String = "Menu") -> NSMenu {
let menu = NSMenu(title: title)
let q = NSMenuItem.init(title: "Quit", action: #selector(app.terminate(_:)), keyEquivalent: "q")
let w = NSMenuItem.init(title: "Close", action: #selector(deactivate(_:)), keyEquivalent: "w")
let o = NSMenuItem.init(title: "Open", action: #selector(activate(_:)), keyEquivalent: "o")
for item in [o,w,q] { menu.addItem(item) }
return menu
}
func applicationDidFinishLaunching(_ n: Notification) { }
func applicationDidHide(_ n: Notification) {
app.setActivationPolicy(.accessory)
DispatchQueue.main.async { self.window.orderOut(self) }
}
}
let app = NSApplication.shared
let delegate = App()
app.delegate = delegate
app.run()
上面的 64 行代码提供了以下功能并修复了以前的解决方案:
- 应用程序菜单可见且可用
- 状态菜单可以点击使用
- 两个菜单都有可用的键绑定
- 打开 window 时出现停靠项目:
app.setActivationPolicy(.regular)
- 停靠项目在 window 关闭时隐藏:
app.setActivationPolicy(.accessory)
- window 在关闭时保留并在打开时重复使用:
window.hidesOnDeactivate = false window.isReleasedWhenClosed = false
在 M1 Pro 上测试。这是它的样子。