I’m seeing unexpected scroll behavior when embedding a LazyVStack with dynamically sized views inside a ScrollView.
Everything works fine when the item height is fixed (e.g. colored squares), but when I switch to text views with variable height, the scroll position jumps and glitches—especially when the keyboard appears or disappears. This only happens on iOS 26, it works fine on iOS 18.
Working version
struct Model: Identifiable {
let id = UUID()
}
struct ModernScrollView: View {
@State private var models: [Model] = []
@State private var scrollPositionID: String?
@State private var text: String = ""
@FocusState private var isFocused
// MARK: - View
var body: some View {
scrollView
.safeAreaInset(edge: .bottom) { controls }
.task { reset() }
}
// MARK: - Subviews
private var scrollView: some View {
ScrollView {
LazyVStack {
ForEach(models) { model in
SquareView(color: Color(from: model.id))
.id(model.id.uuidString)
}
}
.scrollTargetLayout()
}
.scrollPosition(id: $scrollPositionID)
.scrollDismissesKeyboard(.interactively)
.defaultScrollAnchor(.bottom)
.onTapGesture {
isFocused = false
}
}
private var controls: some View {
VStack {
HStack {
Button("Add to top") {
models.insert(contentsOf: makeModels(3), at: 0)
}
Button("Add to bottom") {
models.append(contentsOf: makeModels(3))
}
Button("Reset") {
reset()
}
}
HStack {
Button {
scrollPositionID = models.first?.id.uuidString
} label: {
Image(systemName: "arrow.up")
}
Button {
scrollPositionID = models.last?.id.uuidString
} label: {
Image(systemName: "arrow.down")
}
}
TextField("Input", text: $text)
.padding()
.background(.ultraThinMaterial, in: .capsule)
.focused($isFocused)
.padding(.horizontal)
}
.padding(.vertical)
.buttonStyle(.bordered)
.background(.regularMaterial)
}
// MARK: - Private
private func makeModels(_ count: Int) -> [Model] {
(0..<count).map { _ in Model() }
}
private func reset() {
models = makeModels(3)
}
}
// MARK: - Color+UUID
private extension Color {
init(from uuid: UUID) {
let hash = uuid.uuidString.hashValue
let r = Double((hash & 0xFF0000) >> 16) / 255.0
let g = Double((hash & 0x00FF00) >> 8) / 255.0
let b = Double(hash & 0x0000FF) / 255.0
self.init(red: abs(r), green: abs(g), blue: abs(b))
}
}
Not working version
When I replace the square view with a text view that generates random multiline text:
struct Model: Identifiable {
let id = UUID()
let text = generateRandomText(range: 1...5)
// MARK: - Utils
private static func generateRandomText(range: ClosedRange<Int>) -> String {
var result = ""
for _ in 0..<Int.random(in: range) {
if let sentence = sentences.randomElement() {
result += sentence
}
}
return result.trimmingCharacters(in: .whitespaces)
}
private static let sentences = [
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
]
}
and use it like this:
ForEach(models) { model in
Text(model.text)
.padding()
.multilineTextAlignment(.leading)
.background(Color(from: model.id))
.id(model.id.uuidString)
}
Then on iOS 26, opening the keyboard makes the scroll position jump unpredictably.
It is more visible if you play with the app, but I could not upload a video here.
Environment
Xcode 26.0.1 - Simulators and devices on iOS 26.0 - 18.0
Questions
- Is there any known change in
ScrollView/scrollPosition(id:)behavior on iOS 26 related to dynamic height content? - Am I missing something in the layout setup that makes this layout unstable with variable-height cells?
- Is there a workaround or recommended approach for keeping scroll position stable when keyboard appears?