命令行 Swift 脚本的最低限度可行的 GUI 是什么?
What is the minimally-viable GUI for command-line Swift scripts?
我的 Swift macOS 脚本需要一些辅助 GUI。它只需要一个文本输入字段和一个确定按钮。
我不想为了这个小弹出窗口走完所有臃肿的 Xcode 路线。但是,Apple 的文档让我失望,因为我的 NSWindow 没有捕获键盘输入。求助!
不感谢 Apple's documentation,我终于想出了从命令行启动简单 AppKit/Cocoa GUI Swift 应用程序 所需的魔法咒语 ♂️接受键盘输入。 没有Xcode!
这也是在 WKWebViews 中接受文本输入所必需的。
// main.swift // Dylan Sharhon // Tested on Catalina, Nov 2019
import AppKit // import Cocoa if you also need Foundation functionality
let app = NSApplication.shared
app.setActivationPolicy(.regular) // Magic to accept keyboard input and be docked!
let window = NSWindow.init(
contentRect: NSRect(x: 300, y: 300, width: 200, height: 85),
styleMask: [
NSWindow.StyleMask.titled // Magic needed to accept keyboard input
],
backing: NSWindow.BackingStoreType.buffered,
defer: false
)
window.makeKeyAndOrderFront(nil) // Magic needed to display the window
// Text input field
let text = NSTextField.init(string: "")
text.frame = NSRect(x: 10, y: 45, width: 180, height: 25)
window.contentView!.addSubview(text)
// Button
class Target {
@objc func onClick () { // Magic @objc needed for the button action
print(text.stringValue) // Goes to stdout
exit(0)
}
}
let target = Target()
let button = NSButton.init(
title: "OK",
target: target,
action: #selector(Target.onClick)
)
button.frame = NSRect(x:50, y:10, width:100, height:30)
window.contentView!.addSubview(button)
app.run()
致像我这样的菜鸟:这是整个应用程序,您可以使用 swift main.swift
运行 或使用 swiftc main.swift
编译它,将生成的(仅 40 KB)可执行文件重命名为您想要在菜单栏中显示的任何内容。
我写了一个小脚本,可以在 swift 中显示 window。目标是显示几个 shell 命令的输出(brew update
& brew upgrade
& brew cleanup
& brew doctor
)。如果您不是每天都执行这些命令,那么这些命令可能会花费大量时间,而且我已经厌倦了仅仅为了完成前 2 个命令就必须等待 10 分钟。
我本可以简单地启动一个 cron 作业或将 launchd 与 shell 脚本一起使用,但我希望能够检查命令的成功或失败,尤其是 brew doctor
,以知道我是否需要执行一些操作来清理我机器上的自制程序安装。
所以我需要一个 window 来显示命令的错误和标准输出等等,我想生成它的二进制文件。
在 Google 和 Github 上稍作搜索后,我找到了 swift-sh,它允许导入 Github 存储库(以标准化的方式通过 Swift 包管理器)并在 swift 脚本中使用它并在需要时编译它;和 ShellOut,由同一个人开发,它允许从 swift 脚本执行 shell 命令,并在 swift 对象中收集命令的输出。
基本上,它应该有点 window 在滚动视图中带有文本视图,它显示了 shell 命令的输出,同时能够滚动它。
这里是脚本:
#!/usr/bin/swift sh
import AppKit
import Foundation
// importing ShellOut from GitHub repository
// The magic of swift-sh happens
import ShellOut // @JohnSundell
// Declare the Application context
let app = NSApplication.shared
// Create the delegate class responsible for the window and crontrol creation
class AppDelegate: NSObject, NSApplicationDelegate {
var str: String? = ""
// Construct the window
let theWindow = NSWindow(contentRect: NSMakeRect(200, 200, 400, 200),
styleMask: [.titled, .closable, .miniaturizable, .resizable],
backing: .buffered,
defer: false,
screen: nil)
var output: String? = ""
// What happens once application context launched
func applicationDidFinishLaunching(_ notification: Notification) {
var str = ""
// The shell commands and the collect of output
do {
str = try shellOut(to: "brew", arguments: ["update"] )
output = output! + str
} catch {
let error1 = error as! ShellOutError
//print(error1.message)
output = output! + error1.message
}
do {
str = try shellOut(to: "brew", arguments: ["upgrade"] )
output = output! + "\n" + str
//print("step 2")
} catch {
let error2 = error as! ShellOutError
//print(error2.message)
output = output! + "\n" + error2.message
}
do {
str = try shellOut(to: "brew", arguments: ["cleanup"] )
output = output! + "\n" + str
//print("step 3")
} catch {
let error3 = error as! ShellOutError
//print(error3.message)
output = output! + "\n" + error3.message
}
do {
str = try shellOut(to: "brew", arguments: ["doctor"] )
output = output! + "\n" + str
//print("step 4")
} catch {
let error4 = error as! ShellOutError
//print(error4.message)
output = output! + "\n" + error4.message
}
// Controls placement and content goes here
// ScrollView...
var theScrollview = NSScrollView(frame: theWindow.contentView!.bounds)
var contentSize = theScrollview.contentSize
theScrollview.borderType = .noBorder
theScrollview.hasVerticalScroller = true
theScrollview.hasHorizontalScroller = false
theScrollview.autoresizingMask = NSView.AutoresizingMask(rawValue: NSView.AutoresizingMask.width.rawValue | NSView.AutoresizingMask.height.rawValue | NSView.AutoresizingMask.minYMargin.rawValue | NSView.AutoresizingMask.minYMargin.rawValue)
// TextView...
var theTextView = NSTextView(frame: NSMakeRect(0, 0, contentSize.width, contentSize.height))
theTextView.minSize = NSMakeSize(0.0, contentSize.height)
theTextView.maxSize = NSMakeSize(CGFloat.greatestFiniteMagnitude, CGFloat.greatestFiniteMagnitude)
theTextView.isVerticallyResizable = true
theTextView.isHorizontallyResizable = false
theTextView.autoresizingMask = NSView.AutoresizingMask(rawValue: NSView.AutoresizingMask.width.rawValue | NSView.AutoresizingMask.height.rawValue | NSView.AutoresizingMask.minYMargin.rawValue | NSView.AutoresizingMask.minYMargin.rawValue)
theTextView.textContainer?.containerSize = NSMakeSize(contentSize.width, CGFloat.greatestFiniteMagnitude)
theTextView.backgroundColor = .white
theTextView.textContainer?.widthTracksTextView = true
theTextView.textStorage?.append(NSAttributedString(string: output!))
theScrollview.documentView = theTextView
theWindow.contentView = theScrollview
theWindow.makeKeyAndOrderFront(nil)
theWindow.makeFirstResponder(theTextView)
}
// What happens when we click the close button of the window
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true;
}
}
// Instantiation of the application delegate class
// and launching
let delegate = AppDelegate()
app.delegate = delegate
app.run()
我的 Swift macOS 脚本需要一些辅助 GUI。它只需要一个文本输入字段和一个确定按钮。
我不想为了这个小弹出窗口走完所有臃肿的 Xcode 路线。但是,Apple 的文档让我失望,因为我的 NSWindow 没有捕获键盘输入。求助!
不感谢 Apple's documentation,我终于想出了从命令行启动简单 AppKit/Cocoa GUI Swift 应用程序 所需的魔法咒语 ♂️接受键盘输入。 没有Xcode!
这也是在 WKWebViews 中接受文本输入所必需的。
// main.swift // Dylan Sharhon // Tested on Catalina, Nov 2019
import AppKit // import Cocoa if you also need Foundation functionality
let app = NSApplication.shared
app.setActivationPolicy(.regular) // Magic to accept keyboard input and be docked!
let window = NSWindow.init(
contentRect: NSRect(x: 300, y: 300, width: 200, height: 85),
styleMask: [
NSWindow.StyleMask.titled // Magic needed to accept keyboard input
],
backing: NSWindow.BackingStoreType.buffered,
defer: false
)
window.makeKeyAndOrderFront(nil) // Magic needed to display the window
// Text input field
let text = NSTextField.init(string: "")
text.frame = NSRect(x: 10, y: 45, width: 180, height: 25)
window.contentView!.addSubview(text)
// Button
class Target {
@objc func onClick () { // Magic @objc needed for the button action
print(text.stringValue) // Goes to stdout
exit(0)
}
}
let target = Target()
let button = NSButton.init(
title: "OK",
target: target,
action: #selector(Target.onClick)
)
button.frame = NSRect(x:50, y:10, width:100, height:30)
window.contentView!.addSubview(button)
app.run()
致像我这样的菜鸟:这是整个应用程序,您可以使用 swift main.swift
运行 或使用 swiftc main.swift
编译它,将生成的(仅 40 KB)可执行文件重命名为您想要在菜单栏中显示的任何内容。
我写了一个小脚本,可以在 swift 中显示 window。目标是显示几个 shell 命令的输出(brew update
& brew upgrade
& brew cleanup
& brew doctor
)。如果您不是每天都执行这些命令,那么这些命令可能会花费大量时间,而且我已经厌倦了仅仅为了完成前 2 个命令就必须等待 10 分钟。
我本可以简单地启动一个 cron 作业或将 launchd 与 shell 脚本一起使用,但我希望能够检查命令的成功或失败,尤其是 brew doctor
,以知道我是否需要执行一些操作来清理我机器上的自制程序安装。
所以我需要一个 window 来显示命令的错误和标准输出等等,我想生成它的二进制文件。
在 Google 和 Github 上稍作搜索后,我找到了 swift-sh,它允许导入 Github 存储库(以标准化的方式通过 Swift 包管理器)并在 swift 脚本中使用它并在需要时编译它;和 ShellOut,由同一个人开发,它允许从 swift 脚本执行 shell 命令,并在 swift 对象中收集命令的输出。
基本上,它应该有点 window 在滚动视图中带有文本视图,它显示了 shell 命令的输出,同时能够滚动它。
这里是脚本:
#!/usr/bin/swift sh
import AppKit
import Foundation
// importing ShellOut from GitHub repository
// The magic of swift-sh happens
import ShellOut // @JohnSundell
// Declare the Application context
let app = NSApplication.shared
// Create the delegate class responsible for the window and crontrol creation
class AppDelegate: NSObject, NSApplicationDelegate {
var str: String? = ""
// Construct the window
let theWindow = NSWindow(contentRect: NSMakeRect(200, 200, 400, 200),
styleMask: [.titled, .closable, .miniaturizable, .resizable],
backing: .buffered,
defer: false,
screen: nil)
var output: String? = ""
// What happens once application context launched
func applicationDidFinishLaunching(_ notification: Notification) {
var str = ""
// The shell commands and the collect of output
do {
str = try shellOut(to: "brew", arguments: ["update"] )
output = output! + str
} catch {
let error1 = error as! ShellOutError
//print(error1.message)
output = output! + error1.message
}
do {
str = try shellOut(to: "brew", arguments: ["upgrade"] )
output = output! + "\n" + str
//print("step 2")
} catch {
let error2 = error as! ShellOutError
//print(error2.message)
output = output! + "\n" + error2.message
}
do {
str = try shellOut(to: "brew", arguments: ["cleanup"] )
output = output! + "\n" + str
//print("step 3")
} catch {
let error3 = error as! ShellOutError
//print(error3.message)
output = output! + "\n" + error3.message
}
do {
str = try shellOut(to: "brew", arguments: ["doctor"] )
output = output! + "\n" + str
//print("step 4")
} catch {
let error4 = error as! ShellOutError
//print(error4.message)
output = output! + "\n" + error4.message
}
// Controls placement and content goes here
// ScrollView...
var theScrollview = NSScrollView(frame: theWindow.contentView!.bounds)
var contentSize = theScrollview.contentSize
theScrollview.borderType = .noBorder
theScrollview.hasVerticalScroller = true
theScrollview.hasHorizontalScroller = false
theScrollview.autoresizingMask = NSView.AutoresizingMask(rawValue: NSView.AutoresizingMask.width.rawValue | NSView.AutoresizingMask.height.rawValue | NSView.AutoresizingMask.minYMargin.rawValue | NSView.AutoresizingMask.minYMargin.rawValue)
// TextView...
var theTextView = NSTextView(frame: NSMakeRect(0, 0, contentSize.width, contentSize.height))
theTextView.minSize = NSMakeSize(0.0, contentSize.height)
theTextView.maxSize = NSMakeSize(CGFloat.greatestFiniteMagnitude, CGFloat.greatestFiniteMagnitude)
theTextView.isVerticallyResizable = true
theTextView.isHorizontallyResizable = false
theTextView.autoresizingMask = NSView.AutoresizingMask(rawValue: NSView.AutoresizingMask.width.rawValue | NSView.AutoresizingMask.height.rawValue | NSView.AutoresizingMask.minYMargin.rawValue | NSView.AutoresizingMask.minYMargin.rawValue)
theTextView.textContainer?.containerSize = NSMakeSize(contentSize.width, CGFloat.greatestFiniteMagnitude)
theTextView.backgroundColor = .white
theTextView.textContainer?.widthTracksTextView = true
theTextView.textStorage?.append(NSAttributedString(string: output!))
theScrollview.documentView = theTextView
theWindow.contentView = theScrollview
theWindow.makeKeyAndOrderFront(nil)
theWindow.makeFirstResponder(theTextView)
}
// What happens when we click the close button of the window
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true;
}
}
// Instantiation of the application delegate class
// and launching
let delegate = AppDelegate()
app.delegate = delegate
app.run()