{"id":13466436,"url":"https://github.com/sindresorhus/Settings","last_synced_at":"2025-03-25T21:32:15.206Z","repository":{"id":44849210,"uuid":"139150459","full_name":"sindresorhus/Settings","owner":"sindresorhus","description":"⚙ Add a settings window to your macOS app in minutes","archived":false,"fork":false,"pushed_at":"2024-06-05T11:07:25.000Z","size":7327,"stargazers_count":1445,"open_issues_count":27,"forks_count":101,"subscribers_count":20,"default_branch":"main","last_synced_at":"2024-10-29T17:57:40.559Z","etag":null,"topics":["carthage","cocoapods","macos","preferences","preferences-window","settings","swift-package","swift-package-manager","swiftui","swiftui-components"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sindresorhus.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"license","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"sindresorhus","open_collective":"sindresorhus","buy_me_a_coffee":"sindresorhus","custom":"https://sindresorhus.com/donate"}},"created_at":"2018-06-29T13:05:08.000Z","updated_at":"2024-10-29T06:55:51.000Z","dependencies_parsed_at":"2023-09-24T03:51:26.137Z","dependency_job_id":"a3b4aa17-9ce8-41f8-8353-cd937517d85d","html_url":"https://github.com/sindresorhus/Settings","commit_stats":{"total_commits":103,"total_committers":18,"mean_commits":5.722222222222222,"dds":"0.33009708737864074","last_synced_commit":"879ea83a7bbc6dbebf62bed8c547f090146372a6"},"previous_names":["sindresorhus/Preferences","sindresorhus/preferences"],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sindresorhus%2FSettings","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sindresorhus%2FSettings/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sindresorhus%2FSettings/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sindresorhus%2FSettings/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sindresorhus","download_url":"https://codeload.github.com/sindresorhus/Settings/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244583416,"owners_count":20476326,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["carthage","cocoapods","macos","preferences","preferences-window","settings","swift-package","swift-package-manager","swiftui","swiftui-components"],"created_at":"2024-07-31T15:00:44.168Z","updated_at":"2025-03-25T21:32:13.914Z","avatar_url":"https://github.com/sindresorhus.png","language":"Swift","readme":"# Settings\n\n\u003e Add a settings window to your macOS app in minutes\n\n\u003cimg src=\"screenshot.gif\" width=\"628\"\u003e\n\nJust pass in some view controllers and this package will take care of the rest. Built-in SwiftUI support.\n\n*This package is compatible with macOS 13 and automatically uses `Settings` instead of `Preferences` in the window title on macOS 13 and later.*\n\n*This project was previously known as `Preferences`.*\n\n## Requirements\n\nmacOS 10.13 and later.\n\n## Install\n\nAdd `https://github.com/sindresorhus/Settings` in the [“Swift Package Manager” tab in Xcode](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app).\n\n## Usage\n\n*Run the `Example` Xcode project to try a live example (requires macOS 11 or later).*\n\nFirst, create some settings pane identifiers:\n\n```swift\nimport Settings\n\nextension Settings.PaneIdentifier {\n\tstatic let general = Self(\"general\")\n\tstatic let advanced = Self(\"advanced\")\n}\n```\n\nSecond, create a couple of view controllers for the settings panes you want. The only difference from implementing a normal view controller is that you have to add the `SettingsPane` protocol and implement the `paneIdentifier`, `toolbarItemTitle`, and `toolbarItemIcon` properties, as shown below. You can leave out `toolbarItemIcon` if you're using the `.segmentedControl` style.\n\n`GeneralSettingsViewController.swift`\n\n```swift\nimport Cocoa\nimport Settings\n\nfinal class GeneralSettingsViewController: NSViewController, SettingsPane {\n\tlet paneIdentifier = Settings.PaneIdentifier.general\n\tlet paneTitle = \"General\"\n\tlet toolbarItemIcon = NSImage(systemSymbolName: \"gearshape\", accessibilityDescription: \"General settings\")!\n\n\toverride var nibName: NSNib.Name? { \"GeneralSettingsViewController\" }\n\n\toverride func viewDidLoad() {\n\t\tsuper.viewDidLoad()\n\n\t\t// Setup stuff here\n\t}\n}\n```\n\nNote: If you need to support macOS versions older than macOS 11, you have to add a [fallback for the `toolbarItemIcon`](#backwards-compatibility).\n\n`AdvancedSettingsViewController.swift`\n\n```swift\nimport Cocoa\nimport Settings\n\nfinal class AdvancedSettingsViewController: NSViewController, SettingsPane {\n\tlet paneIdentifier = Settings.PaneIdentifier.advanced\n\tlet paneTitle = \"Advanced\"\n\tlet toolbarItemIcon = NSImage(systemSymbolName: \"gearshape.2\", accessibilityDescription: \"Advanced settings\")!\n\n\toverride var nibName: NSNib.Name? { \"AdvancedSettingsViewController\" }\n\n\toverride func viewDidLoad() {\n\t\tsuper.viewDidLoad()\n\n\t\t// Setup stuff here\n\t}\n}\n```\n\nIf you need to respond actions indirectly, the settings window controller will forward responder chain actions to the active pane if it responds to that selector.\n\n```swift\nfinal class AdvancedSettingsViewController: NSViewController, SettingsPane {\n\t@IBOutlet private var fontLabel: NSTextField!\n\tprivate var selectedFont = NSFont.systemFont(ofSize: 14)\n\n\t@IBAction private func changeFont(_ sender: NSFontManager) {\n\t\tfont = sender.convert(font)\n\t}\n}\n```\n\nIn the `AppDelegate`, initialize a new `SettingsWindowController` and pass it the view controllers. Then add an action outlet for the `Settings…` menu item to show the settings window.\n\n`AppDelegate.swift`\n\n```swift\nimport Cocoa\nimport Settings\n\n@main\nfinal class AppDelegate: NSObject, NSApplicationDelegate {\n\t@IBOutlet private var window: NSWindow!\n\n\tprivate lazy var settingsWindowController = SettingsWindowController(\n\t\tpanes: [\n\t\t\tGeneralSettingsViewController(),\n\t\t\tAdvancedSettingsViewController()\n\t\t]\n\t)\n\n\tfunc applicationDidFinishLaunching(_ notification: Notification) {}\n\n\t@IBAction\n\tfunc settingsMenuItemActionHandler(_ sender: NSMenuItem) {\n\t\tsettingsWindowController.show()\n\t}\n}\n```\n\n### Settings Tab Styles\n\nWhen you create the `SettingsWindowController`, you can choose between the `NSToolbarItem`-based style (default) and the `NSSegmentedControl`:\n\n```swift\n// …\nprivate lazy var settingsWindowController = SettingsWindowController(\n\tpanes: [\n\t\tGeneralSettingsViewController(),\n\t\tAdvancedSettingsViewController()\n\t],\n\tstyle: .segmentedControl\n)\n// …\n```\n\n`.toolbarItem` style:\n\n![NSToolbarItem based (default)](toolbar-item.png)\n\n`.segmentedControl` style:\n\n![NSSegmentedControl based](segmented-control.png)\n\n## API\n\n```swift\npublic enum Settings {}\n\nextension Settings {\n\tpublic enum Style {\n\t\tcase toolbarItems\n\t\tcase segmentedControl\n\t}\n}\n\npublic protocol SettingsPane: NSViewController {\n\tvar paneIdentifier: Settings.PaneIdentifier { get }\n\tvar paneTitle: String { get }\n\tvar toolbarItemIcon: NSImage { get } // Not required when using the .`segmentedControl` style\n}\n\npublic final class SettingsWindowController: NSWindowController {\n\tinit(\n\t\tpanes: [SettingsPane],\n\t\tstyle: Settings.Style = .toolbarItems,\n\t\tanimated: Bool = true,\n\t\thidesToolbarForSingleItem: Bool = true\n\t)\n\n\tinit(\n\t\tpanes: [SettingsPaneConvertible],\n\t\tstyle: Settings.Style = .toolbarItems,\n\t\tanimated: Bool = true,\n\t\thidesToolbarForSingleItem: Bool = true\n\t)\n\n\tfunc show(pane: Settings.PaneIdentifier? = nil)\n}\n```\n\nAs with any `NSWindowController`, call `NSWindowController#close()` to close the settings window.\n\n## Recommendation\n\nThe easiest way to create the user interface within each pane is to use a [`NSGridView`](https://developer.apple.com/documentation/appkit/nsgridview) in Interface Builder. See the example project in this repo for a demo.\n\n## SwiftUI support\n\nIf your deployment target is macOS 10.15 or later, you can use the bundled SwiftUI components to create panes. Create a `Settings.Pane` (instead of `SettingsPane` when using AppKit) using your custom view and necessary toolbar information.\n\nRun the `Example` target in the Xcode project in this repo to see a real-world example. The `Accounts` tab is in SwiftUI.\n\nThere are also some bundled convenience SwiftUI components, like [`Settings.Container`](Sources/Settings/Container.swift) and [`Settings.Section`](Sources/Settings/Section.swift) to automatically achieve similar alignment to AppKit's [`NSGridView`](https://developer.apple.com/documentation/appkit/nsgridview). And also a `.settiingDescription()` view modifier to style text as a setting description.\n\nTip: The [`Defaults`](https://github.com/sindresorhus/Defaults#swiftui-support) package makes it very easy to persist the settings.\n\n```swift\nstruct CustomPane: View {\n\tvar body: some View {\n\t\tSettings.Container(contentWidth: 450.0) {\n\t\t\tSettings.Section(title: \"Section Title\") {\n\t\t\t\t// Some view.\n\t\t\t}\n\t\t\tSettings.Section(label: {\n\t\t\t\t// Custom label aligned on the right side.\n\t\t\t}) {\n\t\t\t\t// Some view.\n\t\t\t}\n\t\t\t…\n\t\t}\n\t}\n}\n```\n\nThen in the `AppDelegate`, initialize a new `SettingsWindowController` and pass it the pane views.\n\n```swift\n// …\n\nprivate lazy var settingsWindowController = SettingsWindowController(\n\tpanes: [\n\t\tPane(\n\t\t\t identifier: …,\n\t\t\t title: …,\n\t\t\t toolbarIcon: NSImage(…)\n\t\t) {\n\t\t\tCustomPane()\n\t\t},\n\t\tPane(\n\t\t\t identifier: …,\n\t\t\t title: …,\n\t\t\t toolbarIcon: NSImage(…)\n\t\t) {\n\t\t\tAnotherCustomPane()\n\t\t}\n\t]\n)\n\n// …\n```\n\nIf you want to use SwiftUI panes alongside standard AppKit `NSViewController`'s, instead wrap the pane views into `Settings.PaneHostingController` and pass them to `SettingsWindowController` as you would with standard panes.\n\n```swift\nlet CustomViewSettingsPaneViewController: () -\u003e SettingsPane = {\n\tlet paneView = Settings.Pane(\n\t\tidentifier: …,\n\t\ttitle: …,\n\t\ttoolbarIcon: NSImage(…)\n\t) {\n\t\t// Your custom view (and modifiers if needed).\n\t\tCustomPane()\n\t\t//  .environmentObject(someSettingsManager)\n\t}\n\n\treturn Settings.PaneHostingController(paneView: paneView)\n}\n\n// …\n\nprivate lazy var settingsWindowController = SettingsWindowController(\n\tpanes: [\n\t\tGeneralSettingsViewController(),\n\t\tAdvancedSettingsViewController(),\n\t\tCustomViewSettingsPaneViewController()\n\t],\n\tstyle: .segmentedControl\n)\n\n// …\n```\n\n[Full example here.](Example/AccountsScreen.swift).\n\n## Backwards compatibility\n\nmacOS 11 and later supports SF Symbols which can be conveniently used for the toolbar icons. If you need to support older macOS versions, you have to add a fallback. Apple recommends using the same icons even for older systems. The best way to achieve this is to [export the relevant SF Symbols icons](https://github.com/davedelong/sfsymbols) to images and add them to your Asset Catalog.\n\n## Known issues\n\n### The settings window doesn't show\n\nThis can happen when you are not using auto-layout or have not set a size for the view controller. You can fix this by either using auto-layout or setting an explicit size, for example, `preferredContentSize` in `viewDidLoad()`. [We intend to fix this.](https://github.com/sindresorhus/Settings/pull/28)\n\n### There are no animations on macOS 10.13 and earlier\n\nThe `animated` parameter of `SettingsWindowController.init` has no effect on macOS 10.13 or earlier as those versions don't support `NSViewController.TransitionOptions.crossfade`.\n\n## FAQ\n\n### How can I localize the window title?\n\nThe `SettingsWindowController` adheres to the [macOS Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/patterns/settings/) and uses this set of rules to determine the window title:\n\n- **Multiple settings panes:** Uses the currently selected `paneTitle` as the window title. Localize your `paneTitle`s to get localized window titles.\n- **Single settings pane:** Sets the window title to `APPNAME Settings`. The app name is obtained from your app's bundle. You can localize its `Info.plist` to customize the title. The `Settings` part is taken from the \"Settings…\" menu item, see #12. The order of lookup for the app name from your bundle:\n\t1. `CFBundleDisplayName`\n\t2. `CFBundleName`\n\t3. `CFBundleExecutable`\n\t4. Fall back to `\"\u003cUnknown App Name\u003e\"` to show you're missing some settings.\n\n### Why should I use this instead of just manually implementing it myself?\n\nIt can't be that hard right? Well, turns out it is:\n\n- The recommended way is to implement it using storyboards. [But storyboards...](https://gist.github.com/iraycd/01b45c5e1be7ef6957b7) And if you want the segmented control style, you have to implement it programmatically, [which is quite complex](https://github.com/sindresorhus/Settings/blob/85f8d793050004fc0154c7f6a061412e00d13fa3/Sources/Preferences/SegmentedControlStyleViewController.swift).\n- [Even Apple gets it wrong, a lot.](https://twitter.com/sindresorhus/status/1113382212584464384)\n- You have to correctly handle [window](https://github.com/sindresorhus/Settings/commit/cc25d58a9ec379812fc8f2fd7ba48f3d35b4cbff) and [tab restoration](https://github.com/sindresorhus/Settings/commit/2bb3fc7418f3dc49b534fab986807c4e70ba78c3).\n- [The window title format depends on whether you have a single or multiple panes.](https://developer.apple.com/design/human-interface-guidelines/patterns/settings/)\n- It's difficult to get the transition animation right. A lot of apps have flaky animation between panes.\n- You end up having to deal with a lot of gnarly auto-layout complexities.\n\n### How is it better than [`MASPreferences`](https://github.com/shpakovski/MASPreferences)?\n\n- Written in Swift. *(No bridging header!)*\n- Swifty API using a protocol.\n- Supports segmented control style tabs.\n- SwiftUI support.\n- Fully documented.\n- Adheres to the [macOS Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/patterns/settings/).\n- The window title is automatically localized by using the system string.\n\n## Related\n\n- [Defaults](https://github.com/sindresorhus/Defaults) - Swifty and modern UserDefaults\n- [LaunchAtLogin](https://github.com/sindresorhus/LaunchAtLogin) - Add \"Launch at Login\" functionality to your macOS app\n- [KeyboardShortcuts](https://github.com/sindresorhus/KeyboardShortcuts) - Add user-customizable global keyboard shortcuts to your macOS app\n- [DockProgress](https://github.com/sindresorhus/DockProgress) - Show progress in your app's Dock icon\n- [More…](https://github.com/search?q=user%3Asindresorhus+language%3Aswift+archived%3Afalse\u0026type=repositories)\n\nYou might also like Sindre's [apps](https://sindresorhus.com/apps).\n\n## Used in these apps\n\n- [TableFlip](https://tableflipapp.com) - Visual Markdown table editor by [Christian Tietze](https://github.com/DivineDominion)\n- [The Archive](https://zettelkasten.de/the-archive/) - Note-taking app by [Christian Tietze](https://github.com/DivineDominion)\n- [Word Counter](https://wordcounterapp.com) - Measuring writer's productivity by [Christian Tietze](https://github.com/DivineDominion)\n- [Medis](https://getmedis.com) - A Redis GUI by [Zihua Li](https://github.com/luin)\n- [OK JSON](https://okjson.app) - A scriptable JSON formatter by [Francis Feng](https://github.com/francisfeng)\n- [Menu Helper](https://github.com/Kyle-Ye/MenuHelper) - A Finder Extension App by [Kyle-Ye](https://github.com/Kyle-Ye)\n\nWant to tell the world about your app that is using this package? Open a PR!\n\n## Maintainers\n\n- [Sindre Sorhus](https://github.com/sindresorhus)\n- [Christian Tietze](https://github.com/DivineDominion)\n","funding_links":["https://github.com/sponsors/sindresorhus","https://opencollective.com/sindresorhus","https://buymeacoffee.com/sindresorhus","https://sindresorhus.com/donate"],"categories":["Libs","Swift","Recently Updated"],"sub_categories":["UI","[Feb 04, 2025](/content/2025/02/04/README.md)"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsindresorhus%2FSettings","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsindresorhus%2FSettings","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsindresorhus%2FSettings/lists"}