SuperSwift

输入、输出与快捷指令

用 RunContext 读取快捷指令或分享页面传入的输入,并把结果交回去。

你写的每个应用都可以通过 RunContext 接收输入、交出结果。不管应用是直接运行的,还是从快捷指令、分享页面或主屏幕链接启动的,写法都一样。

import SuperSwift

let run = RunContext.current
run.source  // .app、.shortcut、.shareSheet 或 .urlScheme
run.input   // 调用方传入的内容

RunContext.current 始终存在。直接运行时,source 是 .app,输入为空,所以接入快捷指令之前可以先在应用里把代码跑通。

控制台输出不是结果

print 只会写到控制台,用于调试。快捷指令只会收到你传给 run.finish(with:) 的内容,所以增删 print 不会影响快捷指令拿到的结果。

读取输入

输入是一组按原始顺序排列的条目,每个条目是文本、URL 或文件。常见情况可以直接用下面这些属性:

属性类型内容
input.isEmptyBool是否传入了任何内容
input.textString?第一个文本条目
input.texts[String]所有文本条目
input.urls[URL]URL 条目,以及整段内容就是一个 URL 的文本条目
input.files[InputFile]文件,每个都有 name、contentType(UTType 标识,如 public.png)、data 和 text
input.jsonJSONValue?第一个文本条目按 JSON 解析的结果
input.items[RunInputItem]按原始顺序排列的全部条目,每个条目提供 text、url 或 file

JSON 输入

把字典或列表传给 Input 参数时,快捷指令会把它转成 JSON 文本,所以用 input.json 就能拿回原来的结构。可以按 key 或位置取值,再用 string、number、bool、array、object 读出具体的值:

import SuperSwift

let json = RunContext.current.input.json
let bill = json?["bill"]?.number ?? 0
let firstTag = json?["tags"]?[0]?.string

为什么用 JSONValue 而不是 Codable?

输入的结构要到运行时才知道。Swift 标准库和 Foundation 都没有公开的类型来表示结构未知的 JSON,所以 RunContext 改用 JSONValue。它是一个普通的枚举,有 .string、.number、.bool、.array、.object、.null 几种取值。

交出结果

调用 finish 或 fail 结束运行。以第一次调用为准,之后的代码不会执行,定时器也会停止。

run.finish()                                  // 结束,无返回值
run.finish(with: .text("Saved"))              // 文本
run.finish(with: .json(.object([              // 结构化数据
    "total": .number(46),
    "items": .array([.string("a"), .string("b")])
])))
run.finish(with: .url(URL(string: "https://superswift.app")!))
run.finish(with: .files([
    OutputFile(name: "report.csv", contentType: "public.comma-separated-values-text", text: "a,b\n1,2\n")
]))
run.fail("Bill must be positive")             // 以失败结束

运行结束的其他情况:

情况结果
脚本执行到最后一行等同于 finish()
用户关闭了带界面的应用等同于 finish()
调用了 fail(_:)、有未捕获的错误,或代码编译失败运行失败,快捷指令中止并显示错误信息
第二次调用 finish 或 fail被忽略,控制台会输出一条警告

顶层控制流

脚本的顶层代码里不能直接写 if、switch 或循环。把这部分逻辑放进一个函数再调用,写法见下面的示例。

在快捷指令中使用

  1. 在「快捷指令」应用里,添加 Run App 动作。
  2. 选择要运行的应用。导入了 SuperSwift 的应用会标注 Accepts input,并排在前面。动作保存的是应用的标识,所以给应用改名不会影响快捷指令。
  3. 按需填写 Input(文本、数字、词典或列表)和 Files。
  4. 使用动作的输出 Run Result。点它可以选 Text、URL 或 Files。JSON 结果在 Text 里,「获取词典值」可以直接读取。

示例:小费计算器

这个脚本接收 {"bill": 42, "percent": 18},以 JSON 返回小费和总额。

import SuperSwift

func calculate() {
    let run = RunContext.current
    guard let bill = run.input.json?["bill"]?.number else {
        run.fail("Pass a dictionary like {\"bill\": 42}.")
        return
    }
    let percent = run.input.json?["percent"]?.number ?? 15
    let tip = bill * percent / 100
    run.finish(with: .json(.object([
        "tip": .number(tip),
        "total": .number(bill + tip)
    ])))
}

calculate()

在快捷指令里建一个包含 bill 和 percent 的词典,作为 Input 传给 Run App,再对 Run Result → Text 使用「获取词典值」,key 填 total。

从分享页面运行

建一个从共享表单接收输入、再交给你的应用的快捷指令:

  1. 新建一个快捷指令,在详情里打开在共享表单中显示。
  2. 添加 Run App,把 Input 设为快捷指令输入。
  3. 在应用里,网页用 run.input.urls 读取,选中的文字用 run.input.text,图片和文档用 run.input.files。

之后在 Safari 和其他应用的分享页面里就能看到这个快捷指令。

交互式应用

带界面的应用也能返回结果。快捷指令运行这类应用时,会把应用打开并显示界面。快捷指令会一直等到应用调用 finish 或被用户关闭,再带着结果继续往下执行。

import SwiftUI
import SuperSwift

@main
struct DrinkPicker: App {
    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

struct ContentView: View {
    @State private var drink = "Coffee"

    var body: some View {
        Form {
            Picker("Drink", selection: $drink) {
                Text("Coffee").tag("Coffee")
                Text("Tea").tag("Tea")
            }
            Button("Done") {
                RunContext.current.finish(with: .text(drink))
            }
        }
    }
}

在 iOS 17 和 18 上,系统会先请用户确认再打开;在 iOS 26 及以后的版本上会直接打开。

限制

  • 主屏幕链接(superswift://run/…)不会携带输入。任何网页或应用都能打开这种链接,如果允许它传入数据,外部内容就能向你的应用注入输入。
  • 每次运行只交出一种结果:文本、JSON、URL 或文件。
  • 快捷指令在后台运行的脚本本身不设超时,但后台任务运行太久可能会被 iOS 停止。耗时的任务,建议给应用加上界面,让它在前台运行。

本页目录