SwiftUI spotlight overlay framework
| Step-by-step tour | Auto-advancing tutorial |
RocketSim_Recording_iPhone_17_Pro_6.3_2026-04-01_14.44.47.mp4 |
RocketSim_Recording_iPhone_17_Pro_6.3_2026-04-01_14.45.10.mp4 |
- Single-line spotlight presentation
- Declarative sequence builder with step navigation
- Tooltip/accessory views on cutouts with flexible positioning
- Multiple spotlight shapes
- Configurable overlay style and animations
- Works across sheets, popovers, and navigation stacks
- Async/await support
- Optional os.Logger integration
- VoiceOver support
Add Beacon to your project via Xcode:
- File > Add Package Dependencies
- Enter:
https://github.com/mmellau/swift-beacon - Select version and add to your target
Or add to Package.swift:
dependencies: [
.package(url: "https://github.com/mmellau/swift-beacon", from: "0.1.0")
]import Beacon
Image(systemName: "star")
.beaconTarget("star")// Fire-and-forget
Beacon.present("star")
// Multiple targets
Beacon.present("inbox", "compose", "settings")
// When you need the result
let result = await Beacon.presentAsync("star")
switch result {
case .tappedOutside: print("User dismissed")
case .tappedRegion(let id): print("User tapped: \(id)")
case .dismissed: print("Dismissed externally")
}Beacon.dismiss()Chain steps into guided tours:
// Fire-and-forget
Beacon.Sequence.run {
BeaconStep(targets: ["profile"])
BeaconStep(targets: ["search"])
BeaconStep(targets: ["settings"])
}
// When you need to await completion
await Beacon.Sequence.runAsync {
BeaconStep(targets: ["profile"])
BeaconStep(targets: ["settings"])
}
print("Onboarding complete!")Attach custom views to spotlight cutouts:
Beacon.Sequence.run {
BeaconStep(targets: [
BeaconTarget("star", alignment: .top) {
Text("Tap to favorite")
}
])
BeaconStep(targets: ["settings"]) // no tooltip
}Use BeaconTarget for per-cutout configuration:
Beacon.present(
BeaconTarget("inbox", alignment: .top) {
Label("3 new messages", systemImage: "envelope")
},
BeaconTarget("compose", alignment: .bottom) {
Text("Write a new message")
},
"settings" // no tooltip
)Tooltips use SwiftUI's native Alignment (.top, .bottom, .leading, .trailing, and all 9 built-in positions). Fine-tune with offset:
BeaconStep(targets: [
BeaconTarget("star", alignment: .top, offset: CGSize(width: 0, height: -8)) {
Text("Tap to favorite")
}
])// Built-in presets
Beacon.style = .dimmed // Default (55% opacity)
Beacon.style = .light // 30% opacity
Beacon.style = .dark // 75% opacity
// Custom
Beacon.style = BeaconStyle(color: .blue, opacity: 0.6)Image(systemName: "star")
.beaconTarget("star") // Default: circle
Button("Submit")
.beaconTarget("submit", shape: .capsule)
RoundedRectangle(cornerRadius: 12)
.beaconTarget("card", shape: .rectangle(cornerRadius: 12))func handleFirstLaunch() async {
let isFirstLaunch = await checkFirstLaunch()
if isFirstLaunch {
await Beacon.Sequence.runAsync {
BeaconStep(targets: ["welcome"])
BeaconStep(targets: ["main_feature"])
}
await markOnboardingComplete()
}
}- iOS 18.4+
- Swift 6.2+
- Xcode 26.0+
See BeaconDemo for an example app.
MIT License. See LICENSE for details.