swiftship/internal/skills/data/ui/list-patterns/SKILL.md
List patterns: ForEach, sections, swipe actions, search, pull-to-refresh. Use when implementing UI patterns related to lists.
npx skillsauth add abdullah4ai/apple-dev-docs list-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Always provide stable identity for ForEach. Never use .indices for dynamic content.
// Good - stable identity via Identifiable
extension User: Identifiable {
var id: String { userId }
}
ForEach(users) { user in
UserRow(user: user)
}
// Good - stable identity via keypath
ForEach(users, id: \.userId) { user in
UserRow(user: user)
}
// Wrong - indices create static content
ForEach(users.indices, id: \.self) { index in
UserRow(user: users[index]) // Can crash on removal!
}
// Wrong - unstable identity
ForEach(users, id: \.self) { user in
UserRow(user: user) // Only works if User is Hashable and stable
}
Critical: Ensure constant number of views per element in ForEach:
// Good - consistent view count
ForEach(items) { item in
ItemRow(item: item)
}
// Bad - variable view count breaks identity
ForEach(items) { item in
if item.isSpecial {
SpecialRow(item: item)
DetailRow(item: item)
} else {
RegularRow(item: item)
}
}
Avoid inline filtering:
// Bad - unstable identity, changes on every update
ForEach(items.filter { $0.isEnabled }) { item in
ItemRow(item: item)
}
// Good - prefilter and cache
@State private var enabledItems: [Item] = []
var body: some View {
ForEach(enabledItems) { item in
ItemRow(item: item)
}
.onChange(of: items) { _, newItems in
enabledItems = newItems.filter { $0.isEnabled }
}
}
Avoid AnyView in list rows:
// Bad - hides identity, increases cost
ForEach(items) { item in
AnyView(item.isSpecial ? SpecialRow(item: item) : RegularRow(item: item))
}
// Good - Create a unified row view
ForEach(items) { item in
ItemRow(item: item)
}
struct ItemRow: View {
let item: Item
var body: some View {
if item.isSpecial {
SpecialRow(item: item)
} else {
RegularRow(item: item)
}
}
}
Why: Stable identity is critical for performance and animations. Unstable identity causes excessive diffing, broken animations, and potential crashes.
Always convert enumerated sequences to arrays. To be able to use them in a ForEach.
let items = ["A", "B", "C"]
// Correct
ForEach(Array(items.enumerated()), id: \.offset) { index, item in
Text("\(index): \(item)")
}
// Wrong - Doesn't compile, enumerated() isn't an array
ForEach(items.enumerated(), id: \.offset) { index, item in
Text("\(index): \(item)")
}
// Remove default background and separators
List(items) { item in
ItemRow(item: item)
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
.listRowSeparator(.hidden)
}
.listStyle(.plain)
.scrollContentBackground(.hidden)
.background(Color.customBackground)
.environment(\.defaultMinListRowHeight, 1) // Allows custom row heights
List(items) { item in
ItemRow(item: item)
}
.refreshable {
await loadItems()
}
.indices for dynamic content)AnyView in list rows.refreshable for pull-to-refreshtesting
Use for 3D games: racing, 3D sports, board games, marble maze, tower defense, bowling. SceneKit + SceneView architecture, 3D scene hierarchy, physics, game loop, primitives, materials, cameras, particles, audio.
documentation
Game UI patterns: SwiftUI HUD overlays on SpriteKit, menus (main/pause/game-over), virtual joystick/d-pad, score displays, health bars, tutorial onboarding.
tools
Download free game sprites/textures/3D models and generate procedural assets. Covers nw_download_asset tool, texture factories, sprite atlas organization, 3D model loading, and programmatic asset creation.
testing
Use for 2D games: arcade, puzzle, sports, ping pong, platformer, shooter, 2D racing. SpriteKit + SpriteView architecture, scene hierarchy, physics, game loop, audio, particles, game feel.