mirror of
https://github.com/Dimillian/Skills.git
synced 2026-03-25 08:55:54 +00:00
Introduces the 'swiftui-ui-patterns' skill to docs/skills.json, providing best practices and example-driven guidance for building SwiftUI views and components. Adds SKILL.md and a comprehensive set of reference files covering TabView, NavigationStack, sheets, forms, controls, grids, overlays, haptics, focus handling, media, matched transitions, split views, and more.
3.4 KiB
3.4 KiB
TabView
Intent
Use this pattern for a scalable, multi-platform tab architecture with:
- a single source of truth for tab identity and content,
- platform-specific tab sets and sidebar sections,
- dynamic tabs sourced from data,
- an interception hook for special tabs (e.g., compose).
Core architecture
AppTabenum defines identity, labels, icons, and content builder.SidebarSectionsenum groups tabs for sidebar sections.AppViewowns theTabViewand selection binding, and routes tab changes throughupdateTab.
Example: custom binding with side effects
Use this when tab selection needs side effects, like intercepting a special tab to perform an action instead of changing selection.
@MainActor
struct AppView: View {
@Binding var selectedTab: AppTab
var body: some View {
TabView(selection: .init(
get: { selectedTab },
set: { updateTab(with: $0) }
)) {
ForEach(availableSections) { section in
TabSection(section.title) {
ForEach(section.tabs) { tab in
Tab(value: tab) {
tab.makeContentView(
homeTimeline: $timeline,
selectedTab: $selectedTab,
pinnedFilters: $pinnedFilters
)
} label: {
tab.label
}
.tabPlacement(tab.tabPlacement)
}
}
.tabPlacement(.sidebarOnly)
}
}
}
private func updateTab(with newTab: AppTab) {
if newTab == .post {
// Intercept special tabs (compose) instead of changing selection.
presentComposer()
return
}
selectedTab = newTab
}
}
Example: direct binding without side effects
Use this when selection is purely state-driven.
@MainActor
struct AppView: View {
@Binding var selectedTab: AppTab
var body: some View {
TabView(selection: $selectedTab) {
ForEach(availableSections) { section in
TabSection(section.title) {
ForEach(section.tabs) { tab in
Tab(value: tab) {
tab.makeContentView(
homeTimeline: $timeline,
selectedTab: $selectedTab,
pinnedFilters: $pinnedFilters
)
} label: {
tab.label
}
.tabPlacement(tab.tabPlacement)
}
}
.tabPlacement(.sidebarOnly)
}
}
}
}
Design choices to keep
- Centralize tab identity and content in
AppTabwithmakeContentView(...). - Use
Tab(value:)withselectionbinding for state-driven tab selection. - Route selection changes through
updateTabto handle special tabs and scroll-to-top behavior. - Use
TabSection+.tabPlacement(.sidebarOnly)for sidebar structure. - Use
.tabPlacement(.pinned)inAppTab.tabPlacementfor a single pinned tab; this is commonly used for iOS 26.searchabletab content, but can be used for any tab.
Dynamic tabs pattern
SidebarSectionshandles dynamic data tabs.AppTab.anyTimelineFilter(filter:)wraps dynamic tabs in a single enum case.- The enum provides label/icon/title for dynamic tabs via the filter type.
Pitfalls
- Avoid adding ViewModels for tabs; keep state local or in
@Observableservices. - Do not nest
@Observableobjects inside other@Observableobjects. - Ensure
AppTab.idvalues are stable; dynamic cases should hash on stable IDs. - Special tabs (compose) should not change selection.