Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion resources/ios/NativeUIListItemRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ struct NativeUIListItemRenderer: View {

@ObservedObject private var themeStore = NativeUITheme.shared
@Environment(\.colorScheme) private var colorScheme
@Environment(\.nativeUIListRowMargin) private var rowMargin

var body: some View {
let p = node.props
Expand Down Expand Up @@ -111,7 +112,7 @@ struct NativeUIListItemRenderer: View {
)
}
}
.padding(.horizontal, 16)
.padding(.horizontal, rowMargin)
.padding(.vertical, 12)
.background(containerColor != 0 ? Color(argb: containerColor) : Color.clear)
.opacity(disabled ? 0.5 : 1.0)
Expand Down
40 changes: 39 additions & 1 deletion resources/ios/NativeUIListRenderer.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import SwiftUI
import UIKit

/// A decoded swipe-action descriptor (one button on either edge).
private struct SwipeActionSpec: Decodable, Identifiable {
Expand Down Expand Up @@ -28,6 +29,8 @@ private func colorFromHex(_ hex: String) -> Color? {
struct NativeUIListRenderer: View {
let node: NativeUINode

@Environment(\.defaultMinListRowHeight) private var minRowHeight

var body: some View {
let horizontal = node.props.getBool("horizontal")
let showsIndicators = node.props.getBool("shows_indicators", default: true)
Expand Down Expand Up @@ -89,6 +92,7 @@ struct NativeUIListRenderer: View {
}
}
.modifier(GroupedOrPlainListStyle(grouped: grouped))
.environment(\.nativeUIListRowMargin, NativeUIListRowMargin.system)
.modifier(ListBackgroundModifier(node: node))
// `List` has no showsIndicators initializer — the modifier is
// the supported route (iOS 16+).
Expand All @@ -115,7 +119,12 @@ struct NativeUIListRenderer: View {

NodeView(node: child)
.equatable()
.frame(maxWidth: .infinity, alignment: .leading)
// A node fills the height it is offered with its content pinned
// to the top, so a one-line row in a cell taller than itself (the
// list's minimum row height, 52pt on iOS 26) sat high. Keep the
// row at its own height and centre it in that minimum instead.
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, minHeight: minRowHeight, alignment: .leading)
.listRowInsets(EdgeInsets())
// Rows carry SwiftUI's opaque system fill, which paints over the
// list's own background — so hiding the scroll background via
Expand Down Expand Up @@ -276,3 +285,32 @@ private struct ListRefreshModifier: ViewModifier {
}
}
}

/// The horizontal inset of a list row's content: the system's minimum
/// layout margin (16pt on most iPhones, 20pt on the wide ones), which is
/// where iOS puts section headers, footers and its own cell content. Rows
/// pad by it so their text lines up with their headers on every device.
enum NativeUIListRowMargin {
@MainActor
static var system: CGFloat {
let root = UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap(\.windows)
.first(where: \.isKeyWindow)?
.rootViewController

return root?.systemMinimumLayoutMargins.leading ?? 16
}
}

private struct NativeUIListRowMarginKey: EnvironmentKey {
static let defaultValue: CGFloat = 16
}

extension EnvironmentValues {
/// Horizontal padding for list rows; 16pt outside a `native:list`.
var nativeUIListRowMargin: CGFloat {
get { self[NativeUIListRowMarginKey.self] }
set { self[NativeUIListRowMarginKey.self] = newValue }
}
}
Loading