SettingsKit
A declarative SwiftUI package for building settings interfaces with navigation, search, and customizable styling
Install / Use
/learn @Aeastr/SettingsKitREADME
Overview
- Declarative API - Build settings hierarchies with intuitive SwiftUI-style syntax
- Built-in Search - Automatic search functionality with intelligent filtering and scoring
- Multiple Styles - Choose from sidebar, grouped, card, or default presentation styles
- Customizable - Extend with custom styles and search implementations
- Platform Adaptive - Works seamlessly on iOS and macOS with appropriate navigation patterns
Installation
dependencies: [
.package(url: "https://github.com/aeastr/SettingsKit.git", from: "1.0.0")
]
import SettingsKit
Usage
Quick Start
import SwiftUI
import SettingsKit
@Observable
class AppSettings {
var notificationsEnabled = true
var darkMode = false
var username = "Guest"
var fontSize: Double = 14.0
var soundEnabled = true
var autoLockDelay: Double = 300
var hardwareAcceleration = true
}
struct MySettings: SettingsContainer {
@Environment(AppSettings.self) var appSettings
var settingsBody: some SettingsContent {
@Bindable var settings = appSettings
// Plain icon (no colored background)
SettingsGroup("General", systemImage: "gear") {
Toggle("Notifications", isOn: $settings.notificationsEnabled)
Toggle("Dark Mode", isOn: $settings.darkMode)
}
// iOS Settings-style colored icons
SettingsGroup("Appearance") {
Slider(value: $settings.fontSize, in: 10...24, step: 1) {
Text("Font Size: \(Int(settings.fontSize))pt")
}
} icon: {
SettingsIcon("paintbrush", color: .blue)
}
SettingsGroup("Privacy & Security") {
Slider(value: $settings.autoLockDelay, in: 60...3600, step: 60) {
Text("Auto Lock: \(Int(settings.autoLockDelay/60)) min")
}
} icon: {
SettingsIcon("lock.shield", color: .blue)
}
}
}
Settings Container
A SettingsContainer is the root of your settings hierarchy:
struct AppSettings: SettingsContainer {
var settingsBody: some SettingsContent {
// Your settings groups here
}
}
Settings Groups
Groups organize related settings and can be presented as navigation links or inline sections:
// Navigation group (default) - appears as a tappable row
SettingsGroup("Display", systemImage: "sun.max") {
// Settings items...
}
// Inline group - appears as a section header
SettingsGroup("Quick Settings", .inline) {
// Settings items...
}
iOS Settings-Style Icons
For colored icon backgrounds like the iOS Settings app, use the icon: ViewBuilder with SettingsIcon:
SettingsGroup("Airplane Mode") {
Toggle("Enabled", isOn: $airplaneMode)
} icon: {
SettingsIcon("airplane", color: .orange)
}
SettingsGroup("Wi-Fi") {
Text("My Network")
} icon: {
SettingsIcon("wifi", color: .blue)
}
SettingsGroup("Battery") {
Text("94%")
} icon: {
SettingsIcon("battery.100", color: .green)
}
The icon: ViewBuilder accepts any SwiftUI view, so you can create fully custom icons:
SettingsGroup("Custom") {
// content
} icon: {
Circle()
.fill(.purple.gradient)
.frame(width: 29, height: 29)
.overlay {
Image(systemName: "star.fill")
.foregroundStyle(.white)
}
}
Custom Settings Groups
For completely custom UI that doesn't fit the standard settings structure, use CustomSettingsGroup:
CustomSettingsGroup("Advanced Tools", systemImage: "hammer") {
VStack(spacing: 20) {
Text("Your Custom UI")
.font(.largeTitle)
Button("Custom Action") {
performAction()
}
}
.padding()
}
Custom groups are indexed and searchable (by title, icon, and tags), but their content is rendered as-is without indexing individual elements.
Using SwiftUI Views Directly
Inside groups, use standard SwiftUI controls directly:
SettingsGroup("Sound", systemImage: "speaker.wave.2") {
Slider(value: $volume, in: 0...100)
Toggle("Haptic Feedback", isOn: $haptics)
Picker("Output", selection: $audioOutput) {
Text("Speaker").tag(0)
Text("Headphones").tag(1)
}
}
Making Views Searchable with .indexed()
By default, individual views are not indexed for search—only SettingsGroup titles are searchable. To make a view appear in search results, use the .indexed() modifier:
SettingsGroup("Display", systemImage: "sun.max") {
Toggle("Dark Mode", isOn: $darkMode)
.indexed("Dark Mode", tags: ["theme", "appearance"])
Slider(value: $brightness, in: 0...1)
.indexed("Brightness")
}
.indexed() API
// Title only
Toggle("Dark Mode", isOn: $dark)
.indexed("Dark Mode")
// Title + additional search tags
Toggle("Dark Mode", isOn: $dark)
.indexed("Dark Mode", tags: ["theme", "night", "appearance"])
// Tags only (useful when title would be redundant)
Toggle("Dark Mode", isOn: $dark)
.indexed(tags: ["Dark Mode", "theme", "appearance"])
Reusable Tag Sets
Define tag sets to keep tagging consistent across your app:
struct ThemeTags: SettingsTagSet {
var tags: [String] { ["theme", "appearance", "display", "colors"] }
}
struct AccessibilityTags: SettingsTagSet {
var tags: [String] { ["accessibility", "a11y", "vision", "motor"] }
}
// Use them
Toggle("Dark Mode", isOn: $dark)
.indexed("Dark Mode", tagSet: ThemeTags())
// Combine multiple tag sets
Toggle("High Contrast", isOn: $highContrast)
.indexed("High Contrast", tagSets: ThemeTags(), AccessibilityTags())
Nested Navigation
Groups can contain other groups for deep hierarchies:
SettingsGroup("General", systemImage: "gear") {
SettingsGroup("About", systemImage: "info.circle") {
Text("Version: 1.0.0")
Text("Build: 42")
}
SettingsGroup("Language", systemImage: "globe") {
Picker("Language", selection: $language) {
Text("English").tag("en")
Text("Spanish").tag("es")
}
}
}
Extracted Settings Groups
Extract complex groups into separate structures:
struct DeveloperSettings: SettingsContent {
@Bindable var settings: AppSettings
var body: some SettingsContent {
SettingsGroup("Developer", systemImage: "hammer") {
Toggle("Debug Mode", isOn: $settings.debugMode)
if settings.debugMode {
Toggle("Verbose Logging", isOn: $settings.verboseLogging)
}
}
}
}
// Use it in your main settings
var settingsBody: some SettingsContent {
DeveloperSettings(settings: settings)
}
Conditional Content
Show or hide settings based on state:
SettingsGroup("Advanced", systemImage: "gearshape.2") {
Toggle("Enable Advanced Features", isOn: $showAdvanced)
if showAdvanced {
Toggle("Advanced Option 1", isOn: $option1)
Toggle("Advanced Option 2", isOn: $option2)
}
}
Customization
Built-in Styles
Sidebar Style (Default) - Split-view navigation:
MySettings(settings: settings)
.settingsStyle(.sidebar)
Single Column Style - Clean, single-column list:
MySettings(settings: settings)
.settingsStyle(.single)
Custom Styles
Create your own presentation styles by conforming to SettingsStyle:
struct MyCustomStyle: SettingsStyle {
func makeContainer(configuration: ContainerConfiguration) -> some View {
NavigationStack(path: configuration.navigationPath) {
ScrollView {
VStack(spacing: 20) {
configuration.content
}
.padding()
}
.navigationTitle(configuration.title)
}
}
func makeGroup(configuration: GroupConfiguration) -> some View {
VStack(alignment: .leading) {
configuration.label
.font(.headline)
configuration.content
}
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(12)
}
func makeItem(configuration: ItemConfiguration) -> some View {
HStack {
configuration.label
Spacer()
configuration.content
}
}
}
// Apply your custom style
MySettings(settings: settings)
.settingsStyle(MyCustomStyle())
Search
Search is automatic and works out of the box. SettingsGroup titles are always searchable. Use .indexed() on individual views to make them searchable too.
Adding Tags to Groups
Related Skills
node-connect
346.8kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
107.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
346.8kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
346.8kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
