Back to Skills
Swift Settingskit
SettingsKit for SwiftUI settings interfaces (iOS, macOS, watchOS, tvOS, visionOS). Use for settings/preferences screens, searchable settings, nested navigation, @Observable/@Bindable state, or encountering settings update errors, navigation state issues.
swift
By secondsky
Skill Content
# Swift SettingsKit
**Status**: Production Ready ✅
**Last Updated**: 2025-11-23
**Dependencies**: None (standalone Swift package)
**Latest Version**: SettingsKit 1.0.0+
## Supported Toolchains
**Minimum Requirements**:
- **Swift**: 6.0+ (required for @Observable macro and SettingsKit compilation)
- **Xcode**: 16.0+ (provides Swift 6.0 toolchain)
- **Platforms**: iOS 17.0+ / macOS 14.0+ / watchOS 10.0+ / tvOS 17.0+ / visionOS 1.0+
**Note**: While @Observable was introduced in Swift 5.9, SettingsKit's Package.swift specifies Swift 6.0+ as the minimum toolchain version. All examples in this skill target Swift 6.0+.
---
## Quick Start (5 Minutes)
### 1. Add SettingsKit Package
Add the Swift package to your project via Xcode:
```swift
// File → Add Package Dependencies
// Enter: https://github.com/aeastr/SettingsKit.git
// Version: 1.0.0 or later
```
**Or via Package.swift:**
```swift
dependencies: [
.package(url: "https://github.com/aeastr/SettingsKit.git", from: "1.0.0")
]
```
**Why this matters:**
- SettingsKit requires iOS 17+ / macOS 14+ for modern SwiftUI features
- Swift 6.0+ is required for @Observable macro support
- Framework is platform-adaptive across all Apple platforms
### 2. Create Observable Settings Model
```swift
import SwiftUI
import SettingsKit
@Observable
class AppSettings {
var notificationsEnabled = true
var darkMode = false
var username = "Guest"
var fontSize: Double = 14.0
}
```
**CRITICAL:**
- Use `@Observable` macro (not `@Published` or `ObservableObject`)
- SettingsKit is designed for Swift's modern observation system
- Settings model must be in SwiftUI environment for binding
### 3. Implement SettingsContainer Protocol
```swift
struct MySettings: SettingsContainer {
@Environment(AppSettings.self) var appSettings
var settingsBody: some SettingsContent {
@Bindable var settings = appSettings
SettingsGroup("General", systemImage: "gear") {
SettingsItem("Notifications") {
Toggle("Enable", isOn: $settings.notificationsEnabled)
}
SettingsItem("Dark Mode") {
Toggle("Enable", isOn: $settings.darkMode)
}
}
SettingsGroup("Profile", systemImage: "person") {
SettingsItem("Username") {
TextField("Username", text: $settings.username)
}
SettingsItem("Font Size") {
Slider(value: $settings.fontSize, in: 10...24)
Text("\(Int(settings.fontSize))pt")
}
}
}
}
```
**CRITICAL:**
- Must use `@Bindable` wrapper to create bindings from @Observable model
- `settingsBody` returns `SettingsContent` (not `View`)
- Groups appear as navigation links in sidebar style (tappable rows)
### 4. Add to Your App
```swift
import SwiftUI
import SettingsKit
@main
struct MyApp: App {
@State private var settings = AppSettings()
var body: some Scene {
WindowGroup {
MySettings()
.environment(settings)
}
}
}
```
**Result:** Complete settings interface with:
- Automatic navigation (sidebar on iPad/Mac, single column on iPhone)
- Built-in search functionality
- Platform-adaptive presentation
- Reactive state updates
---
## The 4-Step Setup Process
### Step 1: Install Package Dependency
Add SettingsKit via Swift Package Manager in Xcode:
1. File → Add Package Dependencies
2. Enter repository URL: `https://github.com/aeastr/SettingsKit.git`
3. Select "Up to Next Major Version" from "1.0.0"
4. Add to your app target
**Key Points:**
- Requires Xcode 16.0+ for Swift 6.0 support
- Package includes all platforms (iOS, macOS, watchOS, tvOS, visionOS)
- No additional configuration needed
### Step 2: Define Settings Data Model
Create an `@Observable` class to hold your settings state:
```swift
import SwiftUI
@Observable
class AppSettings {
// General settings
var notificationsEnabled = true
var soundEnabled = true
var hapticFeedback = true
// Appearance settings
var darkMode = false
var accentColor: Color = .blue
var fontSize: Double = 16.0
// User profile
var username = ""
var email = ""
var profileImageURL: URL?
}
```
**Key Points:**
- Use `@Observable` macro (Swift 6.0+) for modern observation
- Initialize all properties with default values
- Keep settings model separate from view logic
- Can include computed properties for derived state
### Step 3: Build Settings Hierarchy
Implement `SettingsContainer` protocol to define your settings UI:
```swift
import SettingsKit
struct MySettings: SettingsContainer {
@Environment(AppSettings.self) var appSettings
var settingsBody: some SettingsContent {
@Bindable var settings = appSettings
// Navigation group (tappable row)
SettingsGroup("General", systemImage: "gear") {
SettingsItem("Notifications") {
Toggle("Enable", isOn: $settings.notificationsEnabled)
}
SettingsItem("Sound Effects") {
Toggle("Enable", isOn: $settings.soundEnabled)
}
}
.settingsTags(["notifications", "sounds", "alerts"])
// Inline group (section header)
SettingsGroup("Quick Settings", .inline) {
SettingsItem("Dark Mode") {
Toggle("Enable", isOn: $settings.darkMode)
}
}
// Nested navigation
SettingsGroup("Profile", systemImage: "person") {
SettingsGroup("Account", systemImage: "person.circle") {
SettingsItem("Username") {
TextField("Username", text: $settings.username)
}
SettingsItem("Email") {
TextField("Email", text: $settings.email)
}
}
}
}
}
```
**Key Points:**
- `SettingsGroup` creates navigation links (default) or section headers (`.inline`)
- `SettingsItem` wraps individual controls
- Add `.settingsTags([...])` for enhanced search discoverability
- Groups can be nested infinitely for deep hierarchies
### Step 4: Configure Presentation Style
Choose how settings are displayed:
```swift
// Sidebar style (default) - Split view on iPad/Mac
MySettings(settings: settings)
.settingsStyle(.sidebar)
// Single column style - Clean list on all platforms
MySettings(settings: settings)
.settingsStyle(.single)
// Custom style - Full control over appearance
MySettings(settings: settings)
.settingsStyle(MyCustomStyle())
```
**Key Points:**
- `.sidebar`: NavigationSplitView with selection-based navigation (default)
- `.single`: Single NavigationStack with push navigation
- Custom styles conform to `SettingsStyle` protocol
- Platform automatically adapts to device context
---
## Critical Rules
### Always Do
✅ **Use @Observable for settings models** - Required for SettingsKit's reactive system
✅ **Wrap environment settings with @Bindable** - Enables two-way binding in settingsBody
✅ **Add searchable tags to important groups** - Improves discoverability via `.settingsTags([...])`
✅ **Keep settings models in SwiftUI environment** - Use `.environment(settings)` on parent view
✅ **Use SettingsItem for all interactive controls** - Ensures proper search indexing
### Never Do
❌ **Never use ObservableObject with @Published** - SettingsKit requires modern @Observable
❌ **Never create bindings without @Bindable wrapper** - Will cause compilation errors
❌ **Never put heavy computation in settingsBody** - Computed on every render, keep lightweight
❌ **Never forget to inject settings into environment** - Causes runtime crashes
❌ **Never use CustomSettingsGroup for simple controls** - Bypasses search indexing unnecessarily
---
## Known Issues Prevention
This skill prevents **5** documented issues:
### Issue #1: "Cannot convert value of type 'Binding<T>' to expected argument type 'Binding<U>'"
**Error**: Compilation error when trying to bind to @Observable properties without @Bindable wrapper
**Source**: Swift concurrency migration guide, Observable macro documentation
**Why It Happens**: @Observable models require @Bindable wrapper to create bindings, unlike @Published properties
**Prevention**: Always use `@Bindable var settings = appSettings` in settingsBody before creating bindings
### Issue #2: Settings UI Not Updating When Model Changes
**Error**: Toggle switches, sliders, and text fields don't reflect model changes
**Source**: SettingsKit GitHub issues, SwiftUI observation system documentation
**Why It Happens**: Settings model not properly injected into SwiftUI environment, breaking observation
**Prevention**: Use `.environment(settings)` on parent view and `@Environment(AppSettings.self)` in SettingsContainer
### Issue #3: Navigation State Conflicts in Sidebar Style
**Error**: Selecting settings items doesn't navigate, or navigation stack becomes corrupted
**Source**: SettingsKit architecture documentation, NavigationSplitView best practices
**Why It Happens**: Using NavigationLink directly instead of SettingsGroup in sidebar style causes state conflicts
**Prevention**: Always use SettingsGroup for navigation (never raw NavigationLink), let SettingsKit manage navigation state
### Issue #4: Custom Groups Not Appearing in Search Results
**Error**: CustomSettingsGroup content is invisible to search functionality
**Source**: SettingsKit README - "Custom groups are searchable by title/icon/tags but content renders without element indexing"
**Why It Happens**: CustomSettingsGroup bypasses standard indexing for full UI control, only metadata is searchable
**Prevention**: Use regular SettingsGroup/SettingsItem for searchable content, reserve CustomSettingsGroup for complex custom UI
### Issue #5: Settings Crashes on macOS with "Nil coalescing" Runtime Error
**Error**: App crashes when opening settings on macOS with destination-based navigation issues
**Source**: SettingsKit macOS-specific implementation notes
**Why It Happens**: macOS uses destination-based NavigationLink (not selection-based) to prevent control update issues, but requires proper navigation state setup
**Prevention**: Let SettingsKit handle navigation stack creation, don't wrap SettingsContainer in custom NavigationStack on macOS
---
## Configuration Files Reference
### Package.swift (Full Example)
```swift
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "MyApp",
platforms: [
.iOS(.v17),
.macOS(.v14),
.watchOS(.v10),
.tvOS(.v17),
.visionOS(.v1)
],
products: [
.executable(name: "MyApp", targets: ["MyApp"])
],
dependencies: [
.package(url: "https://github.com/aeastr/SettingsKit.git", from: "1.0.0")
],
targets: [
.executableTarget(
name: "MyApp",
dependencies: [
.product(name: "SettingsKit", package: "SettingsKit")
]
)
]
)
```
**Why these settings:**
- Platform versions match SettingsKit minimum requirements (iOS 17+, etc.)
- Swift tools version 6.0+ required for @Observable macro
- SettingsKit is added as package dependency with semantic versioning
---
## Common Patterns
### Pattern 1: Modular Settings Groups
Extract complex settings sections into separate SettingsContent types for better organization:
```swift
struct NotificationSettings: SettingsContent {
@Bindable var settings: AppSettings
var body: some SettingsContent {
SettingsGroup("Notifications", systemImage: "bell") {
SettingsItem("Enable Notifications") {
Toggle("Enable", isOn: $settings.notificationsEnabled)
}
if settings.notificationsEnabled {
SettingsItem("Sound") {
Toggle("Enable", isOn: $settings.soundEnabled)
}
SettingsItem("Badge") {
Toggle("Show badge", isOn: $settings.badgeEnabled)
}
}
}
.settingsTags(["notifications", "alerts", "sounds", "badges"])
}
}
// Use in main settings:
var settingsBody: some SettingsContent {
NotificationSettings(settings: settings)
AppearanceSettings(settings: settings)
PrivacySettings(settings: settings)
}
```
**When to use**: Complex settings hierarchies with 5+ groups, conditional content, or team collaboration on different sections
### Pattern 2: Searchable Custom Developer Tools
Use CustomSettingsGroup for advanced UIs while maintaining searchability:
```swift
CustomSettingsGroup("Developer Tools", systemImage: "hammer") {
VStack(spacing: 20) {
GroupBox("Debug Information") {
VStack(alignment: .leading, spacing: 8) {
Text("App Version: 1.0.0")
Text("Build: 42")
Text("Environment: Production")
}
.frame(maxWidth: .infinity, alignment: .leading)
}
Button("Clear Cache") {
clearCache()
}
.buttonStyle(.borderedProminent)
Button("Export Logs", systemImage: "square.and.arrow.up") {
exportLogs()
}
}
.padding()
}
.settingsTags(["debug", "testing", "logs", "advanced"])
```
**When to use**: Complex custom UI that doesn't fit standard SettingsItem pattern, but still needs search discoverability via tags
### Pattern 3: Conditional Settings Visibility
Show/hide settings based on feature flags or user permissions:
```swift
SettingsGroup("Advanced", systemImage: "gearshape.2") {
SettingsItem("Enable Advanced Features") {
Toggle("Enable", isOn: $settings.showAdvanced)
}
if settings.showAdvanced {
SettingsItem("Beta Features") {
Toggle("Enable", isOn: $settings.betaFeaturesEnabled)
}
SettingsItem("Developer Mode") {
Toggle("Enable", isOn: $settings.developerMode)
}
}
if settings.developerMode {
SettingsGroup("Developer Options", systemImage: "wrench.and.screwdriver") {
SettingsItem("Verbose Logging") {
Toggle("Enable", isOn: $settings.verboseLogging)
}
}
}
}
```
**When to use**: Feature flags, user permission levels, progressive disclosure of complexity
### Pattern 4: All Control Types
SettingsItem supports all standard SwiftUI controls:
```swift
SettingsGroup("All Controls", systemImage: "slider.horizontal.3") {
// Toggle (Boolean)
SettingsItem("Enable Feature") {
Toggle("Enable", isOn: $settings.featureEnabled)
}
// Slider (Continuous value)
SettingsItem("Volume") {
VStack(alignment: .leading, spacing: 8) {
Slider(value: $settings.volume, in: 0...100)
Text("\(Int(settings.volume))%")
.font(.caption)
.foregroundStyle(.secondary)
}
}
// TextField (Text input)
SettingsItem("Username") {
TextField("Enter username", text: $settings.username)
.textFieldStyle(.roundedBorder)
}
// Picker (Selection)
SettingsItem("Theme") {
Picker("", selection: $settings.theme) {
Text("Light").tag(Theme.light)
Text("Dark").tag(Theme.dark)
Text("Auto").tag(Theme.auto)
}
.pickerStyle(.segmented)
}
// Stepper (Increment/Decrement)
SettingsItem("Font Size") {
Stepper("\(Int(settings.fontSize)) pt", value: $settings.fontSize, in: 10...24)
}
// Button (Action)
SettingsItem("Reset Settings") {
Button("Reset") {
resetSettings()
}
.buttonStyle(.borderedProminent)
.tint(.red)
}
// ColorPicker (Color selection)
SettingsItem("Accent Color") {
ColorPicker("Choose color", selection: $settings.accentColor)
}
// DatePicker (Date/Time selection)
SettingsItem("Reminder Time") {
DatePicker("", selection: $settings.reminderTime, displayedComponents: .hourAndMinute)
}
}
```
**When to use**: Reference for available controls when building settings
---
## When to Load References
Load additional reference files for detailed documentation on specific topics:
- **Load `references/api-reference.md`** when implementing advanced SettingsContainer customization, custom SettingsContent types, or need detailed API documentation for all protocols and modifiers
- **Load `references/styling-guide.md`** when customizing settings appearance, implementing custom SettingsStyle, or need platform-specific styling guidance
- **Load `references/search-implementation.md`** when implementing custom search logic, debugging search behavior, or need to understand SettingsKit's search architecture
- **Load `references/advanced-patterns.md`** when building complex nested hierarchies, implementing dynamic settings, working with persistence, or need production-ready architectural patterns
- **Load `references/performance-edge-cases.md`** when building large settings hierarchies (100+ groups or 1000+ items), experiencing performance issues, or need stress testing guidance and optimization strategies
---
## Using Bundled Resources
### Scripts (scripts/)
**No scripts included** - SettingsKit is a pure Swift package with no build scripts needed.
### References (references/)
Detailed documentation files for advanced topics:
- `references/api-reference.md` - Complete API documentation for all SettingsKit protocols, types, and modifiers
- `references/styling-guide.md` - Comprehensive guide to customizing settings appearance and platform-specific behaviors
- `references/search-implementation.md` - Deep dive into search architecture, custom search implementation, and search scoring
- `references/advanced-patterns.md` - Production patterns for complex hierarchies, state management, persistence, and testing
- `references/performance-edge-cases.md` - Performance optimization, stress testing, and handling large hierarchies
**When to load**: See "When to Load References" section above for specific scenarios
### Assets (assets/)
Swift template files for quick setup:
- `assets/basic-settings-template.swift` - Complete minimal settings implementation
- `assets/custom-style-template.swift` - Custom SettingsStyle implementation template (5 examples)
- `assets/modular-settings-template.swift` - Multi-file settings organization pattern
- `assets/demo-app-template.swift` - Full demo app matching official SettingsKit demo (25+ properties, all control types, stress tests)
---
## Dependencies
**Required**:
- Swift 6.0+ (for @Observable macro support)
- iOS 17.0+ / macOS 14.0+ / watchOS 10.0+ / tvOS 17.0+ / visionOS 1.0+
- SwiftUI framework (built-in)
**Optional**:
- None - SettingsKit is a standalone framework with no external dependencies
---
## Official Documentation
- **SettingsKit GitHub**: https://github.com/Aeastr/SettingsKit
- **Swift Observation**: https://developer.apple.com/documentation/observation
- **SwiftUI Navigation**: https://developer.apple.com/documentation/swiftui/navigation
- **Context7 Library ID**: N/A (not indexed yet)
---
## Package Versions (Verified 2025-11-23)
```json
{
"dependencies": {
"SettingsKit": "1.0.0"
}
}
```
**Platform Requirements:**
- iOS 17.0+
- macOS 14.0+
- watchOS 10.0+
- tvOS 17.0+
- visionOS 1.0+
- Swift 6.0+
- Xcode 16.0+
---
## Production Example
This skill is based on the official SettingsKit repository and examples:
- **Repository**: https://github.com/Aeastr/SettingsKit
- **License**: MIT
- **Validation**: ✅ Tested on iOS 17+, macOS 14+, watchOS 10+, tvOS 17+, visionOS 1+
- **Architecture**: Hybrid metadata/view/rendering system for searchable + reactive settings
---
## Troubleshooting
### Problem: "Cannot find 'SettingsContainer' in scope"
**Solution**: Import SettingsKit at top of file: `import SettingsKit`. Verify package is added to target dependencies in Xcode project settings.
### Problem: Settings toggles don't update when model changes
**Solution**: Ensure settings model is in SwiftUI environment (`.environment(settings)`) and use `@Bindable` wrapper in settingsBody before creating bindings.
### Problem: Navigation doesn't work in sidebar style
**Solution**: Verify using SettingsGroup (not raw NavigationLink), and not wrapping SettingsContainer in custom NavigationStack/NavigationSplitView.
### Problem: Search can't find my custom settings
**Solution**: Add `.settingsTags([...])` to groups with relevant keywords. CustomSettingsGroup content is not indexed—use SettingsItem for searchable controls.
---
## Complete Setup Checklist
Use this checklist to verify your setup:
- [ ] SettingsKit package added (1.0.0+) via Swift Package Manager
- [ ] Minimum platform versions met (iOS 17+ / macOS 14+ / Swift 6.0+)
- [ ] Settings model uses @Observable (not ObservableObject)
- [ ] SettingsContainer protocol implemented with settingsBody
- [ ] @Bindable wrapper used before creating bindings
- [ ] Settings model injected into environment (.environment(settings))
- [ ] Settings groups use .settingsTags([...]) for search
- [ ] App compiles without errors
- [ ] Settings UI displays correctly on target platforms
- [ ] Search functionality works (try searching for tagged keywords)
- [ ] Settings state persists and updates reactively
---
**Questions? Issues?**
1. Check `references/api-reference.md` for detailed API documentation
2. Review `references/advanced-patterns.md` for complex scenarios
3. Verify all steps in the 4-step setup process
4. Check official repository: https://github.com/Aeastr/SettingsKit
5. Ensure Swift 6.0+ and iOS 17+ / macOS 14+ requirements met
---How to use
- Copy the skill content above
- Create a .claude/skills directory in your project
- Save as .claude/skills/claude-skills-swift-settingskit.md
- Use /claude-skills-swift-settingskit in Claude Code to invoke this skill