Inputs, Outputs & Shortcuts
Read input from Shortcuts or the share sheet and hand results back with RunContext.
Every app you write can take input and return a result through RunContext. The same code works however the app is started: run directly, from a shortcut, from the share sheet, or from a Home Screen link.
import SuperSwift
let run = RunContext.current
run.source // .app, .shortcut, .shareSheet or .urlScheme
run.input // what the caller passed inRunContext.current is always available. When you run the app directly, source is .app and the input is empty, so you can test your code before connecting it to a shortcut.
Console output is not a result
print only writes to the console for debugging. A shortcut only receives what you pass to run.finish(with:), so adding or removing a print never changes what the shortcut receives.
Reading input
The input is an ordered list of items. Each item is text, a URL or a file. There are shortcuts for the most common cases:
| Property | Type | Contents |
|---|---|---|
input.isEmpty | Bool | Whether anything was passed in |
input.text | String? | The first text item |
input.texts | [String] | Every text item |
input.urls | [URL] | URL items, plus text items that are exactly one URL |
input.files | [InputFile] | Files, each with name, contentType (a UTType identifier such as public.png), data and text |
input.json | JSONValue? | The first text item parsed as JSON |
input.items | [RunInputItem] | Every item in its original order. Each item exposes text, url or file |
JSON input
Shortcuts turns a dictionary or a list into JSON text when you pass it to the Input parameter, so input.json gives you the structure back. You can index into it with a key or a position, and read values with string, number, bool, array and object:
import SuperSwift
let json = RunContext.current.input.json
let bill = json?["bill"]?.number ?? 0
let firstTag = json?["tags"]?[0]?.stringWhy JSONValue and not Codable?
The input's structure is only known at run time. Neither the Swift standard library nor Foundation has a public type for JSON of unknown shape, so RunContext uses JSONValue instead. It's a plain enum with the cases .string, .number, .bool, .array, .object and .null.
Returning a result
Call finish or fail to end the run. The first call decides the result. Code after it doesn't run, and any timers stop.
run.finish() // done, no result
run.finish(with: .text("Saved")) // text
run.finish(with: .json(.object([ // structured data
"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") // stop with an errorOther ways a run can end:
| What happens | Result |
|---|---|
| A script reaches its last line | Same as finish() |
| The user closes an app that has a user interface | Same as finish() |
fail(_:) is called, an error isn't caught, or the code doesn't compile | The run fails and the shortcut stops with the message |
finish or fail is called a second time | Ignored, and a warning is printed to the console |
Top-level control flow
Top-level code in a script can't contain if, switch or loops. Put that logic in a function and call it, as in the example below.
Using an app in Shortcuts
- In the Shortcuts app, add the Run App action.
- Choose the app to run. Apps that import
SuperSwiftare marked Accepts input and appear first. The action stores the app's identifier, so renaming the app doesn't break the shortcut. - Optionally fill in Input (text, a number, a dictionary or a list) and Files.
- Use the action's output, Run Result. Tap it to choose Text, URL or Files. A JSON result is in Text, and Get Dictionary Value can read it directly.
Example: tip calculator
This script takes {"bill": 42, "percent": 18} and returns the tip and the total as 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()In Shortcuts, create a Dictionary with bill and percent, pass it to Run App as Input, then use Get Dictionary Value with the key total on Run Result → Text.
Running from the share sheet
Make a shortcut that receives input from the share sheet and passes it to your app:
- Create a shortcut and turn on Show in Share Sheet in its details.
- Add Run App and set Input to Shortcut Input.
- In your app, read
run.input.urlsfor web pages,run.input.textfor selected text, orrun.input.filesfor images and documents.
The shortcut then appears in the share sheet of Safari and other apps.
Interactive apps
Apps with a user interface can return a result too. When a shortcut runs one, the app opens on screen. The shortcut waits until the app calls finish or the user closes it, then carries on with the result.
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))
}
}
}
}On iOS 17 and 18, the system asks for confirmation before opening the app. On iOS 26 and later, it opens directly.
Limits
- A Home Screen link (
superswift://run/…) never carries input. Any web page or app can open a link like this, so accepting input there would let outside content inject data into your app. - Each run returns one kind of result: text, JSON, a URL or files.
- A script that runs in the background from a shortcut has no timeout of its own, but iOS can stop background work that runs for too long. For long tasks, give the app a user interface so it runs in the foreground.