As a Mac user, you have undoubtedly noticed that there is a big number of apps that have something in common; a menu option to present a Preferences window, where we can specify and change various settings of an app. When provided, that menu option is on the Mac’s top bar, residing in the menu under the app’s name. The key combination that triggers the appearance of that window is also common among all macOS applications; just hitting the Cmd+, key combination brings up Preferences.
As you realize, presenting such a window is a must-have feature for most apps on macOS. As a developer, knowing how to do so in SwiftUI will soon become a mandatory requirement. This post shows how to achieve that, and present a custom Preferences window with just a few moves.
The Settings view
The first step in order to present a Preferences window is to implement a SwiftUI view that will be containing all configurable settings of the app. That view is what will be shown in Preferences. Note that it’s not mandatory to contain all settings code in one source file. On the contrary, it is recommended to break settings implementation in several files, especially if they are meant to be separated in various categories, so it’s easier to maintain them, and generally keep things as simple as possible.
Unless our app has just a handful of settings, most of the times we will be wanting to display tabs at the top of the view. Tabs group together relevant settings, so users can easily navigate among them and find those they want to change. To get a taste of tabbed settings, all you have to do is to open Preferences in Xcode; that specific window contains several tabs, which in turn contain even more settings available for us to configure.
In programming terms, the first move in order to display tabs in our settings view is to add a TabView. Note a small, yet important detail here though. It’s necessary to give the TabView a certain width and height, as that’s going to be the size of the Preferences window. Omitting to do so will result in a pretty small Preferences window that users won’t be able to resize.
So, having said that, let’s add a TabView in a SwiftUI view, which I call SettingsView:
1 2 3 4 5 6 7 8 9 10 |
struct SettingsView: View { var body: some View { TabView { } .frame(width: 450, height: 250) } } |
In the TabView’s closure we’ll add one by one all tab items; the tabs that we want to display in the Preferences window. I would advice as this point to have a different SwiftUI source file for each category of settings matching to a respective tab.
Consider the hypothetical scenario where the app we are developing should be displaying settings behind three different tabs; Profile, Appearance and Privacy. The equivalent code that makes this happen is shown right next:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
TabView { ProfileSettingsView() .tabItem { Label(“Profile”, systemImage: “person.crop.circle”) } AppearanceSettingsView() .tabItem { Label(“Appearance”, systemImage: “paintpalette”) } PrivacySettingsView() .tabItem { Label(“Privacy”, systemImage: “hand.raised”) } } .frame(width: 450, height: 250) |
Let’s make clear what happens above. At first, the actual settings are being implemented separately in three respective SwiftUI views. In this example, these are the ProfileSettingsView, AppearanceSettingsView, and PrivacySettingsView. In fact, none of them implements real settings in this demonstration, as the actual settings are app-specific, they just display a plain Text view. In the following code you can see the contents of these views:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
struct ProfileSettingsView: View { var body: some View { Text(“Profile Settings”) .font(.title) } } struct AppearanceSettingsView: View { var body: some View { Text(“Appearance Settings”) .font(.title) } } struct PrivacySettingsView: View { var body: some View { Text(“Privacy Settings”) .font(.title) } } |
Besides that, each tab item is created by applying the tabItem(_:)
view modifier on each view like so:
1 2 3 4 5 6 |
ProfileSettingsView() .tabItem { Label(“Profile”, systemImage: “person.crop.circle”) } |
In the tab item’s closure, we add a Label view that contains the text and the image we want to show on each tab. Instead of custom images, I’m using SF Symbols here, and I provide the name of each symbol I want to use as value to the systemImage
argument.
Note: If you want to find out more about the TabView, please take a look at this article.
The above few lines of code are sufficient in order to show a tab view and group settings behind tabs. By having the SettingsView ready, as well as any other related individual view as shown above, it’s time to present the Preferences window.
Presenting the Preferences window
In SwiftUI world, the Preferences window is another scene, different from the default WindowGroup or any other scene we might have in an app. In fact, there is a particular one responsible to do what I am discussing here; to present the Preferences window, which in turn contains a settings view such the one demonstrated in the previous part. It’s called Settings
.
We add the Settings scene in the main struct of the app and after the default WindowGroup as shown next:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@main struct SettingsViewDemoApp: App { var body: some Scene { WindowGroup { ContentView() } Settings { } } } |
In the Settings closure we create an instance of the settings view that implements the settings:
1 2 3 4 5 |
Settings { SettingsView() } |
And that’s all it takes to show the Preferences window. If we run the app after having done all the implementation steps presented previously, we will get the following beautiful Preferences window:
Conclusion
In a nutshell, presenting configurable settings and enabling the Preferences window in a macOS app consists of two distinct steps; to implement a settings view which can be parted by one or multiple files, and to add the Settings scene to the main struct of the app, where you will call the settings view from. What I intentionally did not mention in the previous parts is how to store settings persistently; that’s the other side of the coin when presenting settings. You may use the @AppStorage property wrapper in order to read and write to and from user defaults dictionary. You may use CoreData, or you may even use a custom storing system. I leave it to you to decide how to deal with settings data in your app. Regardless, I hope that you found the above helpful in order to present the Preferences window in your own apps using SwiftUI.
Thank you for reading, enjoy coding and take care! ????