skiptools / skiptools/skip

LazyVStack inside ScrollView produces nested scroll regions on Android

Open
#696 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Swift
Stars
3.2k
Forks
106
Avg merge
6d 13h
Merged PRs (30d)
1

Description

LazyVStack nested inside a ScrollView works as expected on iOS (lazy rows feed into the parent scroller) but on Android produces two competing scroll regions: content above the LazyVStack scrolls independently from the LazyVStack itself. No compile-time or transpile-time warning is emitted; the divergence only surfaces at runtime during UX.

Repro:

import SwiftUI

struct ContentView: View {
    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 16) {
                Text("Header section").font(.headline)
                ForEach(0..<5) { i in
                    Text("Header row \(i)")
                }

                LazyVStack(alignment: .leading, spacing: 8) {
                    Text("List section").font(.headline)
                    ForEach(0..<50) { i in
                        Text("List row \(i)")
                            .frame(maxWidth: .infinity, alignment: .leading)
                            .padding()
                            .background(Color.gray.opacity(0.1))
                    }
                }
            }
            .padding()
        }
    }
}
Expected (iOS behavior)

Single scroll region. The header rows scroll off the top of the screen as the user pulls the list rows into view. LazyVStack lazily materializes its rows but defers scrolling to the parent ScrollView.

Actual (Android behavior)

Two independent scroll regions. The header section stays in place while the LazyVStack scrolls within its own bounded region — and vice versa, scrolling outside the LazyVStack doesn't move the lazy content.

Diagnosis

It looks like LazyVStack is being translated to Compose's LazyColumn, which is itself a scroll container with its own viewport. This may be an anti-pattern in Android.

Suggested fix

When LazyVStack appears inside a ScrollView, translate it to a plain Column rather than LazyColumn, giving up laziness on Android (acceptable: it matches what one would write by hand to avoid the nesting issue) in exchange for correct scroll behavior matching iOS. Or alternatively, emit a transpile-time warning so the divergence isn't silent.

The current workaround in user code is to swap LazyVStack → VStack, which works but defeats the purpose of LazyVStack for users who genuinely want lazy loading on long lists.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the Android translation entry points for LazyVStack inside ScrollView and reproduce the SwiftUI example from the issue. Compare the generated LazyColumn and parent scroll behavior, then add or run a regression test for nested scrolling. Done means the header and list share one scroll region without a silent runtime divergence.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, swift
Domain
mobile-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.