List
A styled list component inspired by Family.co, perfect for settings screens, wallet lists, and navigation patterns. Supports icons, accessories, toggles, and interactive items.
Import
import PrettyUI
Basic Usage
PList {
PListItem("Settings")
PListItem("Profile")
PListItem("Notifications")
}
With Header
PList("Account") {
PListItem("Email", subtitle: "john@example.com")
PListItem("Password")
.accessory(.chevron)
}
List Styles
Three styles are available:
// Inset Grouped (default) - rounded corners with margins
PList("Header", style: .insetGrouped) {
// items...
}
// Grouped - rounded corners, full width
PList("Header", style: .grouped) {
// items...
}
// Plain - no card styling
PList("Header", style: .plain) {
// items...
}
List Items
Basic Item
PListItem("Settings")
With Subtitle
PListItem("Email", subtitle: "john@example.com")
With Icon
PListItem("Profile", subtitle: "John Doe", icon: "person.fill")
PListItem("Notifications", icon: "bell.fill")
Custom Icon Color
PListItem("Delete", icon: "trash.fill", iconColor: .red)
Accessories
Add trailing accessories to indicate actions or state:
// Navigation chevron
PListItem("Settings")
.accessory(.chevron)
// Selection checkmark
PListItem("Selected Option")
.accessory(.checkmark)
// Custom icon
PListItem("Sync Status")
.accessory(.icon("checkmark.icloud"))
// Badge
PListItem("Inbox")
.accessory(.badge("3"))
| Accessory | Description |
|---|---|
.chevron | Right arrow for navigation |
.checkmark | Checkmark for selection |
.icon(String) | Custom SF Symbol |
.badge(String) | Text badge (e.g., counts) |
.none | No accessory |
Interactive Items
Add tap actions to list items:
PListItem("Profile", icon: "person.fill")
.accessory(.chevron)
.onTap {
navigateToProfile()
}
Divider Styles
Control dividers between items:
// Inset (default) - aligned with content
PListItem("Item").divider(.inset)
// Full width
PListItem("Item").divider(.full)
// No divider (use for last item)
PListItem("Last Item").hideDivider()
Toggle Items
Use PListToggleItem for switch controls:
@State private var pushEnabled = true
@State private var emailEnabled = false
PList("Notifications") {
PListToggleItem("Push Notifications", icon: "bell.fill", isOn: $pushEnabled)
PListToggleItem("Email Notifications", icon: "envelope.fill", isOn: $emailEnabled)
.hideDivider()
}
Destructive Items
Mark items as destructive:
PListItem("Delete Account", icon: "trash.fill", iconColor: .red)
.destructive()
.onTap { showDeleteConfirmation() }
Custom Leading Content
Use custom views for leading content:
PListItem("John Doe", subtitle: "Online") {
PAvatar(name: "John Doe")
.size(.sm)
.status(.online)
}
.accessory(.chevron)
List Sections
Group items with headers and footers:
PListSection("Account", footer: "Manage your account settings.") {
PListItem("Profile", icon: "person.fill")
PListItem("Security", icon: "lock.fill")
}
PListSection("Support") {
PListItem("Help Center", icon: "questionmark.circle")
PListItem("Contact Us", icon: "envelope")
}
Footer Text
Add explanatory text below the list:
PList(footer: "This action cannot be undone.") {
PListItem("Delete Account", icon: "trash.fill", iconColor: .red)
.destructive()
.hideDivider()
}
Real-World Examples
Settings Screen
ScrollView {
VStack(spacing: 24) {
PList("Account") {
PListItem("Profile", subtitle: "John Doe", icon: "person.fill")
.accessory(.chevron)
.onTap { }
PListItem("Email", subtitle: "john@example.com", icon: "envelope.fill")
.accessory(.chevron)
.onTap { }
PListItem("Password", icon: "lock.fill")
.accessory(.chevron)
.hideDivider()
.onTap { }
}
PList("Preferences") {
PListToggleItem("Push Notifications", icon: "bell.fill", isOn: $pushEnabled)
PListToggleItem("Dark Mode", icon: "moon.fill", isOn: $darkMode)
PListToggleItem("Sounds", icon: "speaker.wave.2.fill", isOn: $soundEnabled)
.hideDivider()
}
PList(footer: "Deleting your account will remove all data permanently.") {
PListItem("Delete Account", icon: "trash.fill", iconColor: .red)
.destructive()
.hideDivider()
.onTap { showDeleteAlert = true }
}
}
.padding()
}
Wallet List
PList("Wallets") {
PListItem("Main Wallet", subtitle: "0x1234...5678") {
PAvatar(name: "Main")
.size(.sm)
.iconBadge("checkmark.circle.fill", backgroundColor: .green)
}
.accessory(.badge("Primary"))
.onTap { }
PListItem("Trading Wallet", subtitle: "0xabcd...efgh") {
PAvatar(name: "Trading")
.size(.sm)
}
.accessory(.chevron)
.onTap { }
PListItem("Add Wallet", icon: "plus.circle.fill")
.hideDivider()
.onTap { showAddWallet = true }
}
Contact List
PList("Contacts") {
ForEach(contacts) { contact in
PListItem(contact.name, subtitle: contact.status) {
PAvatar(name: contact.name)
.size(.sm)
.status(contact.isOnline ? .online : .offline)
}
.accessory(.chevron)
.onTap { selectContact(contact) }
}
}
Selection List
PList("Choose Theme") {
ForEach(Theme.allCases) { theme in
PListItem(theme.name, icon: theme.icon)
.accessory(selectedTheme == theme ? .checkmark : .none)
.onTap { selectedTheme = theme }
}
}
API Reference
PList
PList(
_ header: String? = nil,
footer: String? = nil,
style: PListStyle = .insetGrouped,
@ViewBuilder content: () -> Content
)
PListItem
// Simple
PListItem(_ title: String, subtitle: String?, icon: String?, iconColor: Color?)
// With custom leading
PListItem(_ title: String, subtitle: String?, ..., @ViewBuilder leading: () -> Leading)
Modifiers:
| Modifier | Type | Description |
|---|---|---|
.accessory(_:) | PListAccessory | Trailing accessory |
.divider(_:) | PListDividerStyle | Divider style |
.hideDivider() | — | Remove divider |
.destructive(_:) | Bool | Mark as destructive |
.onTap(_:) | () -> Void | Tap action |
PListToggleItem
PListToggleItem(
_ title: String,
subtitle: String? = nil,
icon: String? = nil,
iconColor: Color? = nil,
isOn: Binding<Bool>
)
PListSection
PListSection(
_ header: String? = nil,
footer: String? = nil,
@ViewBuilder content: () -> Content
)
Enums
PListStyle
.insetGrouped— Rounded with margins (default).grouped— Rounded, full width.plain— No card styling
PListAccessory
.chevron— Navigation arrow.checkmark— Selection indicator.icon(String)— Custom SF Symbol.badge(String)— Text badge.none— No accessory
PListDividerStyle
.inset— Aligned with content (default).full— Full width.none— No divider