From 1ff49caeeadfd06b64770de5517c1d223ce4d3ee Mon Sep 17 00:00:00 2001 From: Dan Fabulich Date: Tue, 20 May 2025 21:04:01 -0700 Subject: [PATCH 1/2] Add stub declaration for onPreferenceChange This was surprisingly tricky. The SwiftUI declaration says: ```swift public func onPreferenceChange(_ key: K.Type = K.self, perform action: @escaping (K.Value) -> Void) -> some View where K : PreferenceKey, K.Value : Equatable ``` But the Skip transpiler doesn't like that. "Skip does not support the referenced type as a generic constraint" To my surprise, the Skip transpiler refuses to like it even when I add a `//SKIP DECLARE` directive. So, I commented out the K.Value : Equatable constraint. (Xcode's compiler will enforce it anyway.) --- .../SkipUI/Environment/PreferenceKey.swift | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/Sources/SkipUI/SkipUI/Environment/PreferenceKey.swift b/Sources/SkipUI/SkipUI/Environment/PreferenceKey.swift index d208a9b..5f9152f 100644 --- a/Sources/SkipUI/SkipUI/Environment/PreferenceKey.swift +++ b/Sources/SkipUI/SkipUI/Environment/PreferenceKey.swift @@ -168,17 +168,24 @@ struct PreferenceNode: Equatable { extension View { public func preference(key: Any.Type, value: Any) -> any View { - #if SKIP +#if SKIP return ComposeModifierView(targetView: self) { PreferenceValues.shared.contribute(context: $0, key: key, value: value) return ComposeResult.ok } - #else +#else + return self +#endif + } + + // TODO: Add Equatable constraint to K.Value + // SKIP DECLARE: public fun , V: Any> View.onPreferenceChange(key: KClass, perform: (V) -> Unit): View + public func onPreferenceChange(_ key: K.Type = K.self, perform action: @escaping (K.Value) -> Void) -> some View where K : PreferenceKey/*, K.Value : Equatable*/ { return self - #endif } } + #if false @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension PreferenceKey where Self.Value : ExpressibleByNilLiteral { @@ -319,23 +326,6 @@ extension View { } -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) -extension View { - - /// Adds an action to perform when the specified preference key's value - /// changes. - /// - /// - Parameters: - /// - key: The key to monitor for value changes. - /// - action: The action to perform when the value for `key` changes. The - /// `action` closure passes the new value as its parameter. - /// - /// - Returns: A view that triggers `action` when the value for `key` - /// changes. - public func onPreferenceChange(_ key: K.Type = K.self, perform action: @escaping (K.Value) -> Void) -> some View where K : PreferenceKey, K.Value : Equatable { return stubView() } - -} - @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension View { From a192ef171524f5376122d18cb97c11a948609684 Mon Sep 17 00:00:00 2001 From: Dan Fabulich Date: Tue, 20 May 2025 22:01:22 -0700 Subject: [PATCH 2/2] Implemented onPreferenceChange --- .../SkipUI/Environment/PreferenceKey.swift | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Sources/SkipUI/SkipUI/Environment/PreferenceKey.swift b/Sources/SkipUI/SkipUI/Environment/PreferenceKey.swift index 5f9152f..6ec6fbb 100644 --- a/Sources/SkipUI/SkipUI/Environment/PreferenceKey.swift +++ b/Sources/SkipUI/SkipUI/Environment/PreferenceKey.swift @@ -5,17 +5,22 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.ProvidedValue import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.rememberSaveable +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.distinctUntilChanged import kotlin.reflect.full.companionObjectInstance #endif @@ -181,7 +186,34 @@ extension View { // TODO: Add Equatable constraint to K.Value // SKIP DECLARE: public fun , V: Any> View.onPreferenceChange(key: KClass, perform: (V) -> Unit): View public func onPreferenceChange(_ key: K.Type = K.self, perform action: @escaping (K.Value) -> Void) -> some View where K : PreferenceKey/*, K.Value : Equatable*/ { + #if SKIP + return ComposeModifierView(contentView: self) { view, context in + let preferenceState = remember(key) { mutableStateOf(Preference(key: key /*as! K.Type*/)) } + + let collector = remember(key, preferenceState) { + PreferenceCollector(key: key, state: preferenceState) + } + + let currentActionCallback = rememberUpdatedState(action) + + // Port this to Swift somehow?? + + // SKIP INSERT: + // LaunchedEffect(collector.state, key) { + // snapshotFlow { collector.state.value.reduced } + // .distinctUntilChanged() + // .collect { collectedNewValue -> + // currentActionCallback.value(collectedNewValue) + // } + // } + + PreferenceValues.shared.collectPreferences(collectors: [collector as! PreferenceCollector]) { + view.Compose(context: context.content()) + } + } + #else return self + #endif } }