Nib provides two layers for persisting data across app launches: the high-level Settings class with instant cache + async persistence, and the low-level UserDefaults class for direct key-value access.
Settings Class¶
Settings is the recommended way to manage application preferences. It provides instant reads from an in-memory cache and automatic background writes to macOS UserDefaults.
Defining settings¶
Create a Settings object with a dictionary of defaults, then register it with the app:
import nib
def main(app: nib.App):
settings = nib.Settings({
"dark_mode": False,
"font_size": 14,
"username": "",
"volume": 50,
})
app.register_settings(settings)
# Read immediately -- returns cached value
print(f"Dark mode: {settings.dark_mode}")
print(f"Font size: {settings.font_size}")
# Write -- updates cache instantly, persists in background
settings.dark_mode = True
settings.volume = 75
app.build(nib.Text("Settings loaded", padding=24))
nib.run(main)
How it works¶
- On
register_settings(), Nib loads any previously saved values from UserDefaults into the cache. - Reading a setting (e.g.,
settings.dark_mode) returns the cached value instantly -- no blocking I/O. - Writing a setting (e.g.,
settings.dark_mode = True) updates the cache and sends a fire-and-forget write to UserDefaults.
Info
The first register_settings() call blocks briefly (up to 2 seconds) while loading saved values. After that, all reads and writes are non-blocking.
on_load callback¶
If you need to run code after settings have loaded from UserDefaults, use the on_load callback:
def on_settings_loaded():
print("Settings loaded from disk")
theme_label.content = settings.theme
settings = nib.Settings(
{"theme": "light", "auto_save": True},
on_load=on_settings_loaded,
)
app.register_settings(settings)
Reset to defaults¶
# Reset a single setting
settings.reset("font_size")
# Reset all settings
settings.reset()
Get all settings as a dict¶
all_settings = settings.to_dict()
# {"dark_mode": True, "font_size": 14, "username": "", "volume": 75}
Settings-Driven UI¶
Use settings to drive your UI. When a control changes, update the setting. When the app loads, read the setting to set the initial state.
import nib
def main(app: nib.App):
settings = nib.Settings({
"dark_mode": False,
"font_size": 14,
"notifications": True,
})
app.register_settings(settings)
# Build UI driven by settings
dark_toggle = nib.Toggle(
"Dark Mode",
is_on=settings.dark_mode,
on_change=lambda is_on: setattr(settings, "dark_mode", is_on),
)
font_slider = nib.Slider(
"Font Size",
value=settings.font_size,
min_value=10,
max_value=24,
on_change=lambda val: setattr(settings, "font_size", int(val)),
)
notify_toggle = nib.Toggle(
"Notifications",
is_on=settings.notifications,
on_change=lambda is_on: setattr(settings, "notifications", is_on),
)
app.build(
nib.Form(
controls=[
nib.Section(
controls=[dark_toggle, font_slider],
header="Appearance",
),
nib.Section(
controls=[notify_toggle],
header="Alerts",
),
],
style=nib.FormStyle.GROUPED,
padding=16,
)
)
nib.run(main)
Settings Page with Tabs¶
For a native macOS preferences window, create a SettingsPage with one or more SettingsTab items and assign it to app.settings.
import nib
def main(app: nib.App):
app.title = "Preferences App"
app.icon = nib.SFSymbol("gear")
app.width = 300
app.height = 200
settings = nib.Settings({
"dark_mode": False,
"font_size": 14,
"auto_update": True,
})
app.register_settings(settings)
app.settings = nib.SettingsPage(
width=500,
height=400,
title="Preferences",
tabs=[
nib.SettingsTab(
"General",
icon="gear",
content=nib.Form(
controls=[
nib.Toggle("Dark Mode", is_on=settings.dark_mode,
on_change=lambda v: setattr(settings, "dark_mode", v)),
nib.Slider("Font Size", value=settings.font_size,
min_value=10, max_value=24,
on_change=lambda v: setattr(settings, "font_size", int(v))),
],
style=nib.FormStyle.GROUPED,
),
),
nib.SettingsTab(
"Updates",
icon="arrow.triangle.2.circlepath",
content=nib.Form(
controls=[
nib.Toggle("Auto-Update", is_on=settings.auto_update,
on_change=lambda v: setattr(settings, "auto_update", v)),
],
style=nib.FormStyle.GROUPED,
),
),
],
)
# Open settings from a button
app.build(
nib.VStack(
controls=[
nib.Text("Preferences App", font=nib.Font.HEADLINE),
nib.Button("Open Settings", action=app.settings.open),
],
spacing=12,
padding=24,
)
)
nib.run(main)
Opening the settings window¶
- Programmatically: call
app.settings.open() - Keyboard shortcut:
Cmd+,(automatically wired whenapp.settingsis set) - From a menu item:
nib.MenuItem("Settings", action=app.settings.open, icon="gear", shortcut="cmd,,")
SettingsTab parameters¶
| Parameter | Type | Description |
|---|---|---|
title |
str |
Tab title shown in the tab bar |
icon |
str |
SF Symbol name for the tab icon |
content |
View |
The view displayed when the tab is selected |
SettingsPage parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
tabs |
list[SettingsTab] |
[] |
List of tabs |
content |
View |
None |
Single view (creates one "General" tab) |
width |
float |
450 |
Window width |
height |
float |
300 |
Window height |
title |
str |
"Settings" |
Window title |
UserDefaults -- Low-Level Access¶
For direct key-value persistence without the Settings wrapper, use UserDefaults. All reads are blocking with a configurable timeout.
import nib
def main(app: nib.App):
app.title = "UserDefaults Demo"
app.icon = nib.SFSymbol("cylinder.split.1x2")
app.width = 300
app.height = 200
defaults = nib.UserDefaults()
# Write values (fire-and-forget)
defaults.set("username", "john_doe")
defaults.set("login_count", 42)
defaults.set("dark_mode", True)
defaults.set("tags", ["python", "swift"])
# Read values (blocking, up to 5s timeout)
username = defaults.get("username", default="guest")
count = defaults.get("login_count", default=0)
# Check existence
if defaults.contains_key("api_token"):
token = defaults.get("api_token")
# Get all keys with a prefix
settings_keys = defaults.get_keys("settings.")
# Remove a key
defaults.remove("old_key")
# Clear all keys
# defaults.clear()
app.build(
nib.VStack(
controls=[
nib.Text(f"User: {username}", font=nib.Font.HEADLINE),
nib.Text(f"Logins: {count}"),
],
spacing=8,
padding=24,
)
)
nib.run(main)
Supported value types¶
| Python Type | Stored As |
|---|---|
str |
String |
int |
Integer |
float |
Float |
bool |
Boolean |
list |
JSON array |
dict |
JSON dictionary |
bytes |
Base64-encoded data |
Warning
UserDefaults.get() is a blocking call that waits for a round trip to the Swift runtime. Use Settings for performance-sensitive reads.