Community-maintained Agent Skills for complete Swift and Apple-platform app delivery.
72
90%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Medium
Suggest reviewing before use
iOS Human Interface Guidelines patterns for layout, typography, color, accessibility, and feedback in SwiftUI.
Omit spacing: on stacks to get SwiftUI's adaptive default. Only specify an explicit value when you need a deliberate departure from the default — and when you do, stick to the 4pt grid below.
This is a common design convention, not an Apple-prescribed system, but it keeps layouts visually coherent. Avoid inventing values between grid stops.
| Points | Token | Typical use |
|---|---|---|
| 4 | .xxSmall | Tight icon-to-label padding, inline badge offsets |
| 8 | .xSmall | Related elements within a group, compact stack gaps |
| 12 | .small | List row internal padding, label-to-secondary-text |
| 16 | .medium | Standard margin, default section gap |
| 20 | .mediumLarge | Comfortable breathing room between distinct controls |
| 24 | .large | Section separators, card internal padding |
| 32 | .xLarge | Major groupings, header-to-content gap |
| 40 | .xxLarge | Large section breaks |
| 48 | .xxxLarge | Hero/splash spacing, onboarding screens |
enum Spacing {
static let xxSmall: CGFloat = 4
static let xSmall: CGFloat = 8
static let small: CGFloat = 12
static let medium: CGFloat = 16
static let mediumLarge: CGFloat = 20
static let large: CGFloat = 24
static let xLarge: CGFloat = 32
static let xxLarge: CGFloat = 40
static let xxxLarge: CGFloat = 48
}private let standardMargin: CGFloat = 16
private let compactMargin: CGFloat = 8
private let largeMargin: CGFloat = 24
extension EdgeInsets {
static let standard = EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16)
static let listRow = EdgeInsets(top: 12, leading: 16, bottom: 12, trailing: 16)
}ScrollView {
LazyVStack {
ForEach(items) { item in
ItemRow(item: item)
}
}
.padding(.horizontal)
}
.safeAreaInset(edge: .bottom) {
HStack {
Button("Cancel") { }
.buttonStyle(.bordered)
Spacer()
Button("Confirm") { }
.buttonStyle(.borderedProminent)
}
.padding()
.background(.regularMaterial)
}Use horizontalSizeClass to adapt between compact and regular widths:
@Environment(\.horizontalSizeClass) private var sizeClass
private var columns: [GridItem] {
switch sizeClass {
case .compact:
[GridItem(.flexible())]
case .regular:
[GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]
default:
[GridItem(.flexible())]
}
}Use system font styles for automatic Dynamic Type support:
| Style | Size | Weight | Usage |
|---|---|---|---|
.largeTitle | 34pt | Regular | Screen titles |
.title | 28pt | Regular | Section headers |
.title2 | 22pt | Regular | Sub-section headers |
.title3 | 20pt | Regular | Group headers |
.headline | 17pt | Semibold | Row titles |
.body | 17pt | Regular | Primary content |
.callout | 16pt | Regular | Secondary content |
.subheadline | 15pt | Regular | Supporting text |
.footnote | 13pt | Regular | Tertiary info |
.caption | 12pt | Regular | Labels |
.caption2 | 11pt | Regular | Small labels |
extension Font {
static func customBody(_ name: String) -> Font {
.custom(name, size: 17, relativeTo: .body)
}
}Use semantic colors for automatic light/dark mode support:
// Labels
Color.primary // Primary text
Color.secondary // Secondary text
Color(uiColor: .tertiaryLabel)
// Backgrounds
Color(uiColor: .systemBackground)
Color(uiColor: .secondarySystemBackground)
Color(uiColor: .systemGroupedBackground)
// Fills and Separators
Color(uiColor: .systemFill)
Color(uiColor: .separator)// Apply app-wide tint
ContentView()
.tint(.blue)Use .tint(...) or .foregroundStyle(.tint) for interactive elements and Color.red for destructive actions.
Use for iPad/macOS multi-column layouts:
NavigationSplitView {
List(items, selection: $selectedItem) { item in
NavigationLink(value: item) { ItemRow(item: item) }
}
.navigationTitle("Items")
} detail: {
if let item = selectedItem {
ItemDetailView(item: item)
} else {
ContentUnavailableView("Select an Item", systemImage: "sidebar.leading")
}
}Use TabView with a NavigationStack per tab. See the swiftui-navigation skill for full tab patterns.
.toolbar {
ToolbarItem(placement: .topBarLeading) { EditButton() }
ToolbarItemGroup(placement: .topBarTrailing) {
Button("Filter", systemImage: "line.3.horizontal.decrease.circle") { }
Button("Add", systemImage: "plus") { }
}
ToolbarItemGroup(placement: .bottomBar) {
Button("Archive", systemImage: "archivebox") { }
Spacer()
Text("\(itemCount) items").font(.footnote).foregroundStyle(.secondary)
Spacer()
Button("Share", systemImage: "square.and.arrow.up") { }
}
}.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
.searchScopes($searchScope) {
ForEach(SearchScope.allCases, id: \.self) { scope in
Text(scope.rawValue.capitalized).tag(scope)
}
}Prefer SwiftUI's sensoryFeedback(_:trigger:) for state-driven feedback in SwiftUI views.
Button("Save") {
didSave.toggle()
}
.sensoryFeedback(.success, trigger: didSave)
Picker("Sort", selection: $sortOrder) {
Text("Recent").tag(SortOrder.recent)
Text("Popular").tag(SortOrder.popular)
}
.sensoryFeedback(.selection, trigger: sortOrder)Use the UIKit generators only when you need imperative feedback from UIKit or non-SwiftUI integration points.
See the Haptics section below for structured patterns.
VStack(alignment: .leading) {
Text(item.title).font(.headline)
Text(item.subtitle).font(.subheadline).foregroundStyle(.secondary)
HStack {
Image(systemName: "star.fill")
Text("\(item.rating, specifier: "%.1f")")
}
}
.accessibilityElement(children: .combine)
.accessibilityLabel("\(item.title), \(item.subtitle)")
.accessibilityValue("Rating: \(item.rating) stars")
.accessibilityHint("Double tap to view details")
.accessibilityAddTraits(.isButton)Adapt layout for accessibility sizes:
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
var body: some View {
if dynamicTypeSize.isAccessibilitySize {
VStack(alignment: .leading) {
leadingContent
trailingContent
}
} else {
HStack {
leadingContent
Spacer()
trailingContent
}
}
}Use ContentUnavailableView for both:
// Error state
ContentUnavailableView {
Label("Unable to Load", systemImage: "exclamationmark.triangle")
} description: {
Text(error.localizedDescription)
} actions: {
Button("Try Again") { Task { await retry() } }
.buttonStyle(.borderedProminent)
}
// Empty state
ContentUnavailableView {
Label("No Photos", systemImage: "camera")
} description: {
Text("Take your first photo to get started.")
} actions: {
Button("Take Photo") { showCamera = true }
.buttonStyle(.borderedProminent)
}Provide a clean, scalable theming approach that keeps view code semantic and consistent.
Theme object as the source of truth (colors, fonts, spacing).@Environment(Theme.self) in views.primaryBackground, secondaryBackground, label, tint) instead of raw colors.Font.custom(_:size:relativeTo:), or @ScaledMetric for numeric layout values.@MainActor
@Observable
final class Theme {
var tintColor: Color = .blue
var primaryBackground: Color = .white
var secondaryBackground: Color = .gray.opacity(0.1)
var labelColor: Color = .primary
var fontSizeScale: Double = 1.0
}@main
struct MyApp: App {
@State private var theme = Theme()
var body: some Scene {
WindowGroup {
AppView()
.environment(theme)
}
}
}struct ProfileView: View {
@Environment(Theme.self) private var theme
var body: some View {
VStack {
Text("Profile")
.foregroundStyle(theme.labelColor)
}
.background(theme.primaryBackground)
}
}Color values in views; it breaks consistency.@Environment(\.colorScheme) as the only theme control; it should complement your theme.Use haptics sparingly to reinforce user actions (tab selection, refresh, success/error) and respect user preferences.
sensoryFeedback(_:trigger:) in SwiftUI views for state-driven feedback.HapticManager only when UIKit interop or non-view code requires it.struct SaveButton: View {
@State private var saveToken = 0
var body: some View {
Button("Save") {
persistChanges()
saveToken += 1
}
.sensoryFeedback(.success, trigger: saveToken)
}
}@MainActor
final class HapticManager {
static let shared = HapticManager()
enum HapticType {
case buttonPress
case tabSelection
case dataRefresh(intensity: CGFloat)
case notification(UINotificationFeedbackGenerator.FeedbackType)
}
private let selectionGenerator = UISelectionFeedbackGenerator()
private let impactGenerator = UIImpactFeedbackGenerator(style: .heavy)
private let notificationGenerator = UINotificationFeedbackGenerator()
private init() { selectionGenerator.prepare() }
func fire(_ type: HapticType, isEnabled: Bool) {
guard isEnabled else { return }
switch type {
case .buttonPress:
impactGenerator.impactOccurred()
case .tabSelection:
selectionGenerator.selectionChanged()
case let .dataRefresh(intensity):
impactGenerator.impactOccurred(intensity: intensity)
case let .notification(style):
notificationGenerator.notificationOccurred(style)
}
}
}Button("Save") {
HapticManager.shared.fire(.notification(.success), isEnabled: preferences.hapticsEnabled)
}
TabView(selection: $selectedTab) { /* tabs */ }
.onChange(of: selectedTab) { _, _ in
HapticManager.shared.fire(.tabSelection, isEnabled: preferences.hapticTabSelectionEnabled)
}Use the core-haptics skill when the interaction needs a custom waveform,
continuous feedback, live parameter modulation, an AHAP asset, or synchronized
audio and haptics. Keep standard success, warning, impact, and selection feedback
in this reference with sensoryFeedback or UIKit feedback generators.
Core Haptics owns capability fallback, CHHapticEngine lifecycle and reset
recovery, retained players, power behavior, and physical-device verification.
Use matched transitions to create smooth continuity between a source view (thumbnail, avatar) and a destination view (sheet, detail, viewer).
Namespace and a stable ID for the source.matchedTransitionSource + navigationTransition(.zoom(...)) on iOS 18+.matchedGeometryEffect for in-place transitions within a view hierarchy.struct MediaPreview: View {
@Namespace private var namespace
let attachments: [MediaAttachment]
var body: some View {
NavigationStack {
List(attachments) { attachment in
NavigationLink(value: attachment) {
ThumbnailView(attachment: attachment)
.matchedTransitionSource(id: attachment.id, in: namespace)
}
}
.navigationDestination(for: MediaAttachment.self) { item in
MediaViewer(item: item)
.navigationTransition(.zoom(sourceID: item.id, in: namespace))
}
}
}
}struct ToggleBadge: View {
@Namespace private var space
@State private var isOn = false
var body: some View {
Button {
withAnimation(.spring) { isOn.toggle() }
} label: {
Image(systemName: isOn ? "eye" : "eye.slash")
.matchedGeometryEffect(id: "icon", in: space)
}
}
}matchedTransitionSource for supported navigation zoom transitions.withAnimation for state-driven transitions.Use this when a view needs a consistent loading state (skeletons, redaction, empty state) without blocking interaction.
.redacted(reason: .placeholder).ContentUnavailableView when load finishes but data is empty.VStack {
if isLoading {
ForEach(0..<3, id: \.self) { _ in
RowView(model: .placeholder())
}
.redacted(reason: .placeholder)
} else if items.isEmpty {
ContentUnavailableView("No items", systemImage: "tray")
} else {
ForEach(items) { item in RowView(model: item) }
}
}This file covers basic form-focus patterns only. For directional focus, focus sections, scene-focused values, and UIFocusGuide, see the focus-engine skill.
Use @FocusState to control keyboard focus, chain fields, and coordinate focus across complex forms.
onAppear..onSubmit to move focus to the next field..option(Int)).struct AddServerView: View {
@State private var server = ""
@FocusState private var isServerFieldFocused: Bool
var body: some View {
Form {
TextField("Server", text: $server)
.focused($isServerFieldFocused)
}
.onAppear { isServerFieldFocused = true }
}
}struct EditTagView: View {
enum FocusField { case title, symbol, newTag }
@FocusState private var focusedField: FocusField?
var body: some View {
Form {
TextField("Title", text: $title)
.focused($focusedField, equals: .title)
.onSubmit { focusedField = .symbol }
TextField("Symbol", text: $symbol)
.focused($focusedField, equals: .symbol)
.onSubmit { focusedField = .newTag }
}
.onAppear { focusedField = .title }
}
}struct PollView: View {
enum FocusField: Hashable { case option(Int) }
@FocusState private var focused: FocusField?
@State private var options: [String] = ["", ""]
@State private var currentIndex = 0
var body: some View {
ForEach(options.indices, id: \.self) { index in
TextField("Option \(index + 1)", text: $options[index])
.focused($focused, equals: .option(index))
.onSubmit { addOption(at: index) }
}
.onAppear { focused = .option(0) }
}
private func addOption(at index: Int) {
options.append("")
currentIndex = index + 1
Task { @MainActor in
try? await Task.sleep(for: .milliseconds(10))
focused = .option(currentIndex)
}
}
}.scrollDismissesKeyboard(...) when using ScrollView/Form..tessl-plugin
skills
accessorysetupkit
references
activitykit
adattributionkit
references
alarmkit
references
app-clips
app-intents
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
audioaccessorykit
references
authentication
references
avfoundation-audio
references
avkit
background-processing
references
browserenginekit
callkit
references
carplay
cloudkit
contacts-framework
references
core-bluetooth
references
core-data
core-haptics
references
core-motion
references
core-nfc
references
coreml
references
cryptokit
cryptotokenkit
debugging-instruments
device-integrity
references
energykit
references
eventkit
family-controls
references
financekit
focus-engine
gamekit
git-branching-workflow
references
healthkit
references
homekit
references
ios-accessibility
ios-app-workflow
references
ios-ettrace-performance
ios-localization
ios-memgraph-analysis
ios-networking
ios-simulator
references
metrickit
references
musickit
references
natural-language
paperkit
references
passkit
references
pdfkit
pencilkit
references
permissionkit
references
photokit
push-notifications
quicklook
references
realitykit
references
relevancekit
references
scenekit
sensorkit
shazamkit
references
speech-recognition
references
spritekit
storekit
swift-api-design-guidelines
swift-architecture
references
swift-charts
swift-codable
references
swift-code-review
swift-concurrency
swift-formatstyle
references
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-responsive-layout
swiftui-uikit-interop
swiftui-webkit
tabletopkit
tipkit
vision-framework
weatherkit
references