88 lines
2.3 KiB
Markdown
88 lines
2.3 KiB
Markdown
# ScrollView and Lazy stacks
|
||
|
||
## Intent
|
||
|
||
Use `ScrollView` with `LazyVStack`, `LazyHStack`, or `LazyVGrid` when you need custom layout, mixed content, or horizontal/ grid-based scrolling.
|
||
|
||
## Core patterns
|
||
|
||
- Prefer `ScrollView` + `LazyVStack` for chat-like or custom feed layouts.
|
||
- Use `ScrollView(.horizontal)` + `LazyHStack` for chips, tags, avatars, and media strips.
|
||
- Use `LazyVGrid` for icon/media grids; prefer adaptive columns when possible.
|
||
- Use `ScrollViewReader` for scroll-to-top/bottom and anchor-based jumps.
|
||
- Use `safeAreaInset(edge:)` for input bars that should stick above the keyboard.
|
||
|
||
## Example: vertical custom feed
|
||
|
||
```swift
|
||
@MainActor
|
||
struct ConversationView: View {
|
||
private enum Constants { static let bottomAnchor = "bottom" }
|
||
@State private var scrollProxy: ScrollViewProxy?
|
||
|
||
var body: some View {
|
||
ScrollViewReader { proxy in
|
||
ScrollView {
|
||
LazyVStack {
|
||
ForEach(messages) { message in
|
||
MessageRow(message: message)
|
||
.id(message.id)
|
||
}
|
||
Color.clear.frame(height: 1).id(Constants.bottomAnchor)
|
||
}
|
||
.padding(.horizontal, .layoutPadding)
|
||
}
|
||
.safeAreaInset(edge: .bottom) {
|
||
MessageInputBar()
|
||
}
|
||
.onAppear {
|
||
scrollProxy = proxy
|
||
withAnimation {
|
||
proxy.scrollTo(Constants.bottomAnchor, anchor: .bottom)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## Example: horizontal chips
|
||
|
||
```swift
|
||
ScrollView(.horizontal, showsIndicators: false) {
|
||
LazyHStack(spacing: 8) {
|
||
ForEach(chips) { chip in
|
||
ChipView(chip: chip)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## Example: adaptive grid
|
||
|
||
```swift
|
||
let columns = [GridItem(.adaptive(minimum: 120))]
|
||
|
||
ScrollView {
|
||
LazyVGrid(columns: columns, spacing: 8) {
|
||
ForEach(items) { item in
|
||
GridItemView(item: item)
|
||
}
|
||
}
|
||
.padding(8)
|
||
}
|
||
```
|
||
|
||
## Design choices to keep
|
||
|
||
- Use `Lazy*` stacks when item counts are large or unknown.
|
||
- Use non-lazy stacks for small, fixed-size content to avoid lazy overhead.
|
||
- Keep IDs stable when using `ScrollViewReader`.
|
||
- Prefer explicit animations (`withAnimation`) when scrolling to an ID.
|
||
|
||
## Pitfalls
|
||
|
||
- Avoid nesting scroll views of the same axis; it causes gesture conflicts.
|
||
- Don’t combine `List` and `ScrollView` in the same hierarchy without a clear reason.
|
||
- Overuse of `LazyVStack` for tiny content can add unnecessary complexity.
|