Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
90
90%
Does it follow best practices?
Impact
—
Average score across 248 eval scenarios
Advisory
Suggest reviewing before use
Use ScrollView with LazyVStack, LazyHStack, or LazyVGrid when you need custom layout, mixed content, or horizontal/ grid-based scrolling.
ScrollView + LazyVStack for chat-like or custom feed layouts.ScrollView(.horizontal) + LazyHStack for chips, tags, avatars, and media strips.LazyVGrid for icon/media grids; prefer adaptive columns when possible.ScrollPosition for programmatic scrolling: scroll-to-id, scroll-to-edge, and point-based offsets.safeAreaInset(edge:) for input bars that should stick above the keyboard.@MainActor
struct ConversationView: View {
@State private var scrollPosition = ScrollPosition(edge: .bottom)
var body: some View {
ScrollView {
LazyVStack {
ForEach(messages) { message in
MessageRow(message: message)
}
}
.scrollTargetLayout()
.padding(.horizontal, .layoutPadding)
}
.scrollPosition($scrollPosition)
.safeAreaInset(edge: .bottom) {
MessageInputBar()
}
.onChange(of: messages.last?.id) {
withAnimation { scrollPosition.scrollTo(edge: .bottom) }
}
}
}ScrollPosition (iOS 18+) replaces ScrollViewReader for programmatic scrolling. It is declarative, supports bidirectional position tracking, and does not require a closure wrapper.
Setup: Declare state and attach to the scroll view. Apply .scrollTargetLayout() to the inner layout container so SwiftUI can track individual view identities.
@State private var scrollPosition = ScrollPosition(idType: Message.ID.self)
ScrollView {
LazyVStack {
ForEach(messages) { message in
MessageRow(message: message)
}
}
.scrollTargetLayout()
}
.scrollPosition($scrollPosition)Scroll to a specific item:
scrollPosition.scrollTo(id: message.id, anchor: .top)Scroll to an edge:
scrollPosition.scrollTo(edge: .bottom)Read the current position:
if let currentID = scrollPosition.viewID(type: Message.ID.self) {
// The view with this ID is currently at the scroll anchor
}Detect user-initiated scrolls:
.onChange(of: scrollPosition.isPositionedByUser) { _, byUser in
if byUser {
// User scrolled manually -- show "scroll to bottom" button
}
}ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
ForEach(chips) { chip in
ChipView(chip: chip)
}
}
}let columns = [GridItem(.adaptive(minimum: 120))]
ScrollView {
LazyVGrid(columns: columns) {
ForEach(items) { item in
GridItemView(item: item)
}
}
.padding()
}Lazy* stacks when item counts are large or unknown.ScrollPosition tracking; changing IDs causes position jumps.withAnimation) when scrolling to an ID.Configure the visual treatment at scroll view edges (iOS 26+):
ScrollView {
content
}
.scrollEdgeEffectStyle(.soft, for: .top) // Soft fading edge at top
.scrollEdgeEffectStyle(.hard, for: .bottom) // Hard cutoff at bottomScrollEdgeEffectStyle values:
.automatic -- platform default.soft -- soft fading edge effect.hard -- hard cutoff with dividing lineUse scrollEdgeEffectHidden(_:for:) to hide the edge effect entirely.
Duplicates, mirrors, and blurs the view to extend behind safe area edges (iOS 26+):
NavigationSplitView {
sidebar
} detail: {
BannerView()
.backgroundExtensionEffect()
}Use sparingly -- Apple recommends only a single instance for visual clarity and performance. The modifier clips the view to prevent mirror overlap.
Attach a bar view to the safe area edge, integrating with scroll edge effects (iOS 26+):
content
.safeAreaBar(edge: .top) {
FilterBar()
}List and ScrollView in the same hierarchy without a clear reason.LazyVStack for tiny content can add unnecessary complexity.scrollEdgeEffectStyle on the ScrollView, not on inner content.backgroundExtensionEffect() on only one view per screen..tessl-plugin
skills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
references
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-localization
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
references
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
swift-charts
references
swift-codable
swift-concurrency
swift-formatstyle
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-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references