Skip to content

Commit 45bf818

Browse files
committed
Render lazy stacks and yield scrolling to them
1 parent 3638080 commit 45bf818

1 file changed

Lines changed: 64 additions & 4 deletions

File tree

  • Demo/swiftui/src/commonMain/kotlin/com/pureswift/swiftui

Demo/swiftui/src/commonMain/kotlin/com/pureswift/swiftui/Render.kt

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import androidx.compose.foundation.layout.Column as LayoutColumn
5454
import androidx.compose.foundation.layout.fillMaxSize
5555
import androidx.compose.foundation.layout.height
5656
import androidx.compose.foundation.lazy.LazyColumn
57+
import androidx.compose.foundation.lazy.LazyRow
5758
import androidx.compose.foundation.lazy.grid.GridCells
5859
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
5960
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
@@ -306,14 +307,25 @@ private fun RenderResolved(node: ViewNode) {
306307
"Divider" -> HorizontalDivider(modifier = node.composeModifiers())
307308

308309
"ScrollView" -> {
309-
val state = rememberScrollState()
310-
if (node.string("axis") == "horizontal") {
311-
Row(modifier = node.composeModifiers().horizontalScroll(state)) { RenderChildren(node) }
310+
// A lazy stack scrolls itself, and nesting it inside a scrolling
311+
// parent would measure it with unbounded height (a Compose
312+
// crash), so hand the scrolling over to it.
313+
val lone = node.children.singleOrNull { !it.isPresentation() }
314+
if (lone != null && lone.isLazyStack()) {
315+
RenderChild(lone)
312316
} else {
313-
Column(modifier = node.composeModifiers().verticalScroll(state)) { RenderChildren(node) }
317+
val state = rememberScrollState()
318+
if (node.string("axis") == "horizontal") {
319+
Row(modifier = node.composeModifiers().horizontalScroll(state)) { RenderChildren(node) }
320+
} else {
321+
Column(modifier = node.composeModifiers().verticalScroll(state)) { RenderChildren(node) }
322+
}
314323
}
315324
}
316325

326+
"LazyVStack" -> RenderLazyStack(node, vertical = true)
327+
"LazyHStack" -> RenderLazyStack(node, vertical = false)
328+
317329
// A Color greedily fills the space offered it (SwiftUI semantics);
318330
// fillMaxWidth is applied after the chain so an explicit frame width
319331
// still constrains it.
@@ -1554,6 +1566,54 @@ private fun ViewNode.stringArray(key: String): List<String> {
15541566
}
15551567

15561568

1569+
private fun ViewNode.isLazyStack(): Boolean = type == "LazyVStack" || type == "LazyHStack"
1570+
1571+
// A lazy stack fetches only the elements Compose asks for, one JNI call each.
1572+
// Content that wasn't a single ForEach carries no provider and renders eagerly.
1573+
@Composable
1574+
private fun RenderLazyStack(node: ViewNode, vertical: Boolean) {
1575+
val provider = node.long("itemProvider")
1576+
if (provider == null) {
1577+
if (vertical) Column(modifier = node.composeModifiers()) { RenderChildren(node) }
1578+
else Row(modifier = node.composeModifiers()) { RenderChildren(node) }
1579+
return
1580+
}
1581+
val count = node.count ?: 0
1582+
val keys = node.stringArray("keys")
1583+
val spacing = (node.double("spacing") ?: 0.0).dp
1584+
if (vertical) {
1585+
LazyColumn(
1586+
modifier = node.composeModifiers().fillMaxWidth(),
1587+
horizontalAlignment = when (node.string("alignment")) {
1588+
"leading" -> Alignment.Start
1589+
"trailing" -> Alignment.End
1590+
else -> Alignment.CenterHorizontally
1591+
},
1592+
verticalArrangement = Arrangement.spacedBy(spacing),
1593+
) {
1594+
items(count = count, key = { keys.getOrNull(it) ?: it }) { index ->
1595+
val element = remember(provider, index) { SwiftBridge.sink.itemNode(provider, index) }
1596+
element?.let { RenderChild(it) }
1597+
}
1598+
}
1599+
} else {
1600+
LazyRow(
1601+
modifier = node.composeModifiers(),
1602+
verticalAlignment = when (node.string("alignment")) {
1603+
"top" -> Alignment.Top
1604+
"bottom" -> Alignment.Bottom
1605+
else -> Alignment.CenterVertically
1606+
},
1607+
horizontalArrangement = Arrangement.spacedBy(spacing),
1608+
) {
1609+
items(count = count, key = { keys.getOrNull(it) ?: it }) { index ->
1610+
val element = remember(provider, index) { SwiftBridge.sink.itemNode(provider, index) }
1611+
element?.let { RenderChild(it) }
1612+
}
1613+
}
1614+
}
1615+
}
1616+
15571617
@OptIn(ExperimentalMaterial3Api::class)
15581618
@Composable
15591619
private fun RenderList(node: ViewNode) {

0 commit comments

Comments
 (0)