LaunchNotes is a small SwiftUI package for presenting native-feeling "What's New" screens in iOS and macOS apps.
It is built for apps that want release notes to be easy to add, easy to customize, and hard to forget. The default API uses the app bundle version automatically, stores the last seen version in UserDefaults, and presents only when the current version has not been seen.
| Full-screen presentation | Manual sheet presentation |
|---|---|
![]() |
![]() |
Most apps need a simple way to tell users what changed after an update. LaunchNotes keeps that workflow close to SwiftUI:
- Define notes directly where you attach the modifier.
- Use the app version automatically or pass an explicit version.
- Keep multiple release definitions in one place.
- Present automatically, manually, as a sheet, or full-screen.
- Customize tint, layout, icon behavior, actions, and footer links without building a custom screen.
- Swift 6.3
- iOS 26 or later
- macOS 26 or later
Add the package in Xcode:
File > Add Package Dependencies...
Use this repository URL:
https://github.com/pol-cova/LaunchNotes
Then import it where you build your app UI:
import LaunchNotesAttach launchNotes to your root view and list the notes. This uses Bundle.main for the current version.
struct ContentView: View {
var body: some View {
HomeView()
.launchNotes {
LaunchNote("Daily Challenges", "Come back every day for a new challenge.", systemImage: "calendar")
LaunchNote("New Stats", "Track your progress with cleaner charts.", systemImage: "chart.line.uptrend.xyaxis")
LaunchNote("Smoother Animations", "The app now feels faster and more polished.", systemImage: "sparkles")
}
}
}LaunchNotes stores the last seen version under LaunchNotes.lastSeenVersion in UserDefaults.standard. When the resolved version changes, the notes present automatically.
HomeView()
.launchNotes(
title: "Fresh Updates",
subtitle: "Here's what changed in this version.",
buttonTitle: "Looks good",
animation: .bouncy,
presentation: .sheet,
style: .compact
) {
LaunchNote("Daily Challenges", "Come back every day for a new challenge.", systemImage: "calendar")
LaunchNote("New Stats", "Track your progress with cleaner charts.", systemImage: "chart.line.uptrend.xyaxis")
}If you want to control the version yourself, pass version:.
HomeView()
.launchNotes(version: "1.2.0") {
LaunchNote("New Stats", "Cleaner progress charts.", systemImage: "chart.line.uptrend.xyaxis")
}Use LaunchNotesRelease when you want to keep a release history in one place. Put the newest release first. In automatic mode, LaunchNotes presents the first release only when its version has not been seen.
HomeView()
.launchNotes(onFooterAction: showPrivacyInfo) {
LaunchNotesRelease(
"1.2.0",
title: "What's New in 1.2",
subtitle: "A cleaner update for daily use.",
buttonTitle: "Continue",
accentColor: .blue,
footerTitle: "About Privacy"
) {
LaunchNote("Daily Challenges", "Come back every day for a new challenge.", systemImage: "calendar")
LaunchNote("New Stats", "Track your progress with cleaner charts.", systemImage: "chart.line.uptrend.xyaxis")
}
LaunchNotesRelease("1.1.0") {
LaunchNote("Search", "Find previous sessions faster.", systemImage: "magnifyingglass")
}
}Use the binding overload for settings screens, debug menus, onboarding flows, or release history buttons.
struct SettingsView: View {
@State private var showReleaseNotes = false
private let release = LaunchNotesRelease("1.2.0", title: "Release Notes") {
LaunchNote("Daily Challenges", "Come back every day for a new challenge.", systemImage: "calendar")
LaunchNote("New Stats", "Track your progress with cleaner charts.", systemImage: "chart.line.uptrend.xyaxis")
}
var body: some View {
Button("Show Release Notes") {
showReleaseNotes = true
}
.launchNotes(isPresented: $showReleaseNotes, release: release)
}
}Use LaunchNotesStyle to customize the default appearance.
HomeView()
.launchNotes(
presentation: .fullScreen,
style: .prominent
) {
LaunchNote("New Stats", "Cleaner progress charts.", systemImage: "chart.line.uptrend.xyaxis")
}Built-in presets:
.default: clean baseline styling..compact: smaller radius and simple presentation..prominent: shows the header icon and uses a stronger presentation..minimal: simple, quiet styling.
Custom style:
LaunchNotesStyle(
accentColor: .indigo,
cornerRadius: 18,
iconStyle: .circle,
showsHeaderIcon: false,
buttonLayout: .vertical
)You can also set a style higher in the view hierarchy:
HomeView()
.launchNotes(version: "1.2.0", notes: notes)
.launchNotesStyle(.init(accentColor: .green))Release-level tint overrides the style tint:
LaunchNotesRelease("1.2.0", accentColor: .teal) {
LaunchNote("Privacy", "Updated privacy controls.", systemImage: "lock")
}A release can include a footer link, such as "About Privacy". Provide footerTitle on the release and onFooterAction on the modifier.
HomeView()
.launchNotes(onFooterAction: showPrivacyDetails) {
LaunchNotesRelease("1.2.0", footerTitle: "About Privacy") {
LaunchNote("New Stats", "Cleaner progress charts.", systemImage: "chart.line.uptrend.xyaxis")
}
}LaunchNote(
"Title",
"Description",
systemImage: "sparkles"
)LaunchNotesRelease(
"1.2.0",
title: "What's New",
subtitle: "Here's what changed.",
buttonTitle: "Continue",
secondaryButtonTitle: nil,
headerSystemImage: "sparkles",
accentColor: .blue,
footerTitle: "About Privacy"
) {
LaunchNote("New Stats", "Cleaner progress charts.", systemImage: "chart.line.uptrend.xyaxis")
}Omit the version to use the current bundle version:
LaunchNotesRelease(title: "Fresh Updates") {
LaunchNote("New Stats", "Cleaner progress charts.", systemImage: "chart.line.uptrend.xyaxis")
}.launchNotes(
mode: .automatic,
animation: .smooth,
presentation: .sheet
) {
LaunchNote("New Stats", "Cleaner progress charts.", systemImage: "chart.line.uptrend.xyaxis")
}Modes:
.automatic: present when the current version has not been seen..always: present every time the host view appears..manual: never auto-present. Use a binding overload.
Presentations:
.sheet: presents as a sheet..fullScreen: usesfullScreenCoveron iOS. On macOS, it falls back to a sheet.
Animations:
.smooth.bouncy.fade.slide.none
let version = LaunchNotesVersion.current()Resolution order:
CFBundleShortVersionStringCFBundleVersion"1.0"
By default, LaunchNotes stores the seen version using:
LaunchNotesDefaultStorageKey // "LaunchNotes.lastSeenVersion"Use a custom store in tests, previews, or debug environments:
HomeView()
.launchNotes(
version: "1.2.0",
notes: notes,
userDefaults: UserDefaults(suiteName: "PreviewDefaults")!,
storageKey: "preview.lastSeenLaunchNotesVersion"
)Reset during development:
UserDefaults.standard.removeObject(forKey: LaunchNotesDefaultStorageKey)The package includes an iOS demo app in Examples/LaunchNotesDemo.
open LaunchNotes.xcworkspaceUse the LaunchNotesDemo scheme to run the demo app.
See CONTRIBUTING.md.
LaunchNotes is available under the MIT license. See LICENSE.

