From 85a8f9c3b3534bc98d5a9eaf87417db4a60d08f5 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Mon, 24 Aug 2026 17:21:43 +0200 Subject: [PATCH] iOS: only attach pull-to-refresh when the list declares on-refresh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit .refreshable was applied unconditionally, so every native:list — static settings screens and forms included — offered a pull-down spinner that did nothing. Gate the modifier on the on_refresh callback id, matching the Android renderer, which already wraps its PullToRefreshBox in the same check. --- resources/ios/NativeUIListRenderer.swift | 30 +++++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/resources/ios/NativeUIListRenderer.swift b/resources/ios/NativeUIListRenderer.swift index 2e710cd..bcce6fa 100644 --- a/resources/ios/NativeUIListRenderer.swift +++ b/resources/ios/NativeUIListRenderer.swift @@ -91,12 +91,12 @@ struct NativeUIListRenderer: View { // the supported route (iOS 16+). .scrollIndicators(showsIndicators ? .automatic : .hidden) .scrollDismissesKeyboard(.interactively) - .refreshable { - if onRefreshCb != 0 { - NativeElementBridge.sendPressEvent(onRefreshCb, nodeId: nodeId) - try? await Task.sleep(nanoseconds: 1_000_000_000) - } - } + // Attach the refresh control only when the list declares an + // on-refresh handler — an unconditional .refreshable installs + // pull-to-refresh (spinner and all) on every list, including + // static settings/forms where it does nothing. Android's renderer + // already gates its PullToRefreshBox the same way. + .modifier(ListRefreshModifier(onRefreshCb: onRefreshCb, nodeId: nodeId)) } } @@ -234,3 +234,21 @@ private struct ListBackgroundModifier: ViewModifier { } } } + +/// Conditionally attaches pull-to-refresh: only lists with an `on_refresh` +/// callback get the control; every other list scrolls plainly. +private struct ListRefreshModifier: ViewModifier { + let onRefreshCb: Int + let nodeId: Int + + func body(content: Content) -> some View { + if onRefreshCb != 0 { + content.refreshable { + NativeElementBridge.sendPressEvent(onRefreshCb, nodeId: nodeId) + try? await Task.sleep(nanoseconds: 1_000_000_000) + } + } else { + content + } + } +}