输入、输出与快捷指令
用 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.isEmpty | Bool | 是否传入了任何内容 |
input.text | String? | 第一个文本条目 |
input.texts | [String] | 所有文本条目 |
input.urls | [URL] | URL 条目,以及整段内容就是一个 URL 的文本条目 |
input.files | [InputFile] | 文件,每个都有 name、contentType(UTType 标识,如 public.png)、data 和 text |
input.json | JSONValue? | 第一个文本条目按 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 或循环。把这部分逻辑放进一个函数再调用,写法见下面的示例。
在快捷指令中使用
- 在「快捷指令」应用里,添加 Run App 动作。
- 选择要运行的应用。导入了
SuperSwift的应用会标注 Accepts input,并排在前面。动作保存的是应用的标识,所以给应用改名不会影响快捷指令。 - 按需填写 Input(文本、数字、词典或列表)和 Files。
- 使用动作的输出 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。
从分享页面运行
建一个从共享表单接收输入、再交给你的应用的快捷指令:
- 新建一个快捷指令,在详情里打开在共享表单中显示。
- 添加 Run App,把 Input 设为快捷指令输入。
- 在应用里,网页用
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 停止。耗时的任务,建议给应用加上界面,让它在前台运行。