Spinner
github:dominicegginton/spinner
A terminal spinner library for Swift command-line tools. Spinner gives clear progress feedback during long-running tasks, with flexible patterns, formatting, and output control.
Features
- More than 80 built-in spinner patterns
- Custom spinner pattern support
- Configurable render format with pattern, message, and duration tokens
- Built-in completion helpers (
success,error,warning,info) - Runtime updates for pattern, message, speed, color, and format
- Custom output stream support via
SpinnerStream - Signal handling support to restore cursor visibility on interrupts
Install
Add Spinner to your Package.swift dependencies:
.package(url: "https://github.com/dominicegginton/Spinner", from: "1.0.0")Usage
Create, start, and stop a spinner:
import Foundation
import Spinner
let spinner = Spinner(.dots, "Building release artifacts")
spinner.start()
sleep(2) // do work
spinner.stop()Use helper completion states:
spinner.success("Build complete")
spinner.error("Build failed")
spinner.warning("Build completed with warnings")
spinner.info("Build cache reused")Custom Formats
Spinner supports custom render formats where:
{S}renders the animated spinner frame{T}renders the text message{D}renders elapsed duration since start
This makes it straightforward to display elapsed time in CLI
workflows, for example by using a format like
{D} {T} - {S}.
CustomStreams
Spinner wraps output in the SpinnerStream protocol, so
you can direct spinner rendering to your own output implementation
instead of the default STDOUT stream.
This is useful when integrating Spinner with other CLI frameworks that expose their own writable stream types.
import Spinner
struct SwiftCLISpinnerStream: SpinnerStream {
private let stdout: WritableStream
init(stdout: WritableStream) {
self.stdout = stdout
}
func write(string: String, terminator: String) {
stdout.write(string, terminator: terminator)
}
func hideCursor() {
stdout.write("\u{001B}[?25l", terminator: "")
}
func showCursor() {
stdout.write("\u{001B}[?25h", terminator: "")
}
}
let spinner = Spinner(.dots, "Deploying", stream: SwiftCLISpinnerStream(stdout: stdout))The key requirement is to implement write,
hideCursor, and showCursor so Spinner can
render frames correctly and restore terminal cursor state when
finished.
Caveat
Spinner uses a signal trap by default so that if the process is interrupted (for example with Ctrl+C), it can restore cursor visibility before exit.
If your CLI already manages signals, this can conflict with existing
handlers. In that case, provide your own SpinnerSignal
implementation and pass it to the spinner.
import Spinner
import Signals
struct CustomSpinnerSignal: SpinnerSignal {
func trap() {
Signals.trap(signal: .int) { _ in
print("\u{001B}[?25h", terminator: "") // show cursor
exit(0)
}
}
}
let spinner = Spinner(.dots, "Deploying", signal: CustomSpinnerSignal())This lets Spinner integrate cleanly with projects that need strict control over process signal behavior.