【问题标题】:What is the minimally-viable GUI for command-line Swift scripts?命令行 Swift 脚本的最小可行 GUI 是什么?
【发布时间】:2019-11-15 07:44:22
【问题描述】:

我需要一个用于 macOS 上的 Swift 脚本的小助手 GUI。它只需要一个文本输入字段和一个确定按钮。

我不想仅仅为了这个小弹出窗口而走整个臃肿的 Xcode 路线。但是,Apple 的文档让我失望了,因为我的 NSWindow 没有捕获键盘输入。救命!

【问题讨论】:

标签: swift macos cocoa appkit


【解决方案1】:

不,感谢Apple's documentation,我终于找到了从接受键盘输入的命令行 Swift 应用程序启动简单 AppKit/Cocoa GUI 所需的神奇咒语 ?‍♂️。没有 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)可执行文件重命名为您想要的任何内容在菜单栏中。

【讨论】:

  • 如果您在开头添加#!/usr/bin/swift,您也可以将其作为脚本运行。
  • 您需要在 app.run 之前使用app.activate(ignoringOtherApps: true) 才能使应用窗口显示在所有其他应用之上。
【解决方案2】:

我写了一个小脚本,它可以快速显示一个窗口。目标是显示几个 shell 命令的输出(brew update & brew upgrade & brew cleanup & brew doctor)。如果您不是每天都执行这些命令,那么这些命令可能会花费大量时间,而且我已经厌倦了必须等待 10 分钟才能完成前 2 个命令。

我本可以简单地启动一个 cron 作业或使用带有 shell 脚本的 launchd,但我希望能够检查命令的成功或失败,尤其是 brew doctor,以了解我是否需要执行某些操作清理我的机器上安装的自制软件。

所以我需要一个窗口来显示命令的错误和标准输出等等,我想生成它的二进制文件。

在 Google 和 Github 上搜索了一下,我找到了 swift-sh,它允许导入 Github 存储库(通过 Swift 包管理器以标准化的方式)并在 swift 脚本中使用它并在需要时编译它;和 ShellOut,由同一个人开发,它允许从 swift 脚本执行 shell 命令,并将命令的输出收集到 swift 对象中。

基本上,它应该是一个滚动视图中带有文本视图的小窗口,它显示了 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()

【讨论】:

    猜你喜欢
    • 2022-06-15
    • 2012-06-22
    • 1970-01-01
    • 1970-01-01
    • 2011-08-30
    • 1970-01-01
    • 1970-01-01
    • 2023-02-06
    • 2013-08-30
    相关资源
    最近更新 更多