diff --git a/Sources/TokamakCore/Modifiers/OnChangeModifier.swift b/Sources/TokamakCore/Modifiers/OnChangeModifier.swift index b226bf0f..a11d0ed9 100644 --- a/Sources/TokamakCore/Modifiers/OnChangeModifier.swift +++ b/Sources/TokamakCore/Modifiers/OnChangeModifier.swift @@ -16,39 +16,30 @@ // import Foundation +import OpenCombineShim struct OnChangeModifier: ViewModifier { @State - var oldValue: V? = nil + private var oldValue: V? let value: V let initial: Bool let action: (V, V) -> () - init(value: V, initial: Bool, action: @escaping (V, V) -> ()) { - self.value = value - self.initial = initial - self.action = action - - if value != oldValue { - action(oldValue ?? value, value) - oldValue = value - } - } - func body(content: Content) -> some View { content - .task { - if initial { - action(value, value) + .onReceive(Just(value)) { newValue in + // TODO: Fix, when @State if working with in a ViewModifier + // ignore first call when oldValue == nil. For now old value is always nil + if newValue != oldValue { + action(oldValue ?? value, newValue) } oldValue = value } - ._onUpdate { - if value != oldValue { - action(oldValue ?? value, value) + .onAppear { + if initial { + action(value, value) } - oldValue = value } } } diff --git a/Sources/TokamakCore/Modifiers/OnReceiveModifier.swift b/Sources/TokamakCore/Modifiers/OnReceiveModifier.swift index bc2bc8e9..3a48dec6 100644 --- a/Sources/TokamakCore/Modifiers/OnReceiveModifier.swift +++ b/Sources/TokamakCore/Modifiers/OnReceiveModifier.swift @@ -18,17 +18,29 @@ import Foundation import OpenCombineShim -struct OnReceiveModifier: ViewModifier where P.Failure == Never { - @State - var cancellable: AnyCancellable +private struct OnReceiveModifier: ViewModifier where P.Failure == Never { + @ObservedObject + var cancellableHolder = CancellableHolder() init(publisher: P, action: @escaping (P.Output) -> ()) { - _cancellable = State(initialValue: publisher.sink(receiveValue: action)) + cancellableHolder.cancellable = publisher.sink(receiveValue: action) } func body(content: Content) -> some View { - content.onDisappear { - cancellable.cancel() + content + } + + // MARK: Types + + final class CancellableHolder: ObservableObject { + var cancellable: AnyCancellable? { + didSet { + oldValue?.cancel() + } + } + + deinit { + cancellable?.cancel() } } } diff --git a/Tests/TokamakTests/ViewReactToDataChangesTests.swift b/Tests/TokamakTests/ViewReactToDataChangesTests.swift index 38820632..ccd11196 100644 --- a/Tests/TokamakTests/ViewReactToDataChangesTests.swift +++ b/Tests/TokamakTests/ViewReactToDataChangesTests.swift @@ -32,11 +32,11 @@ class ViewModifierTests: XCTestCase { // Simulate publisher emitting a value publisher.send("Testing onReceive") - - // Re-evaluate the view - let reconciler = TestFiberRenderer(.root, size: .zero).render(contentView) - XCTAssertEqual(receivedValue, "Testing onReceive") + + // Simulate publisher emitting a value + publisher.send("Second onReceive") + XCTAssertEqual(receivedValue, "Second onReceive") } func testOnChangeWithValue() {