-
-
Save jegnux/c3aee7957f6c372bf31a46c893a6e2a2 to your computer and use it in GitHub Desktop.
import SwiftUI | |
import Combine | |
public struct ChangeObserver<V: Equatable>: ViewModifier { | |
public init(newValue: V, action: @escaping (V) -> Void) { | |
self.newValue = newValue | |
self.newAction = action | |
} | |
private typealias Action = (V) -> Void | |
private let newValue: V | |
private let newAction: Action | |
@State private var state: (V, Action)? | |
public func body(content: Content) -> some View { | |
if #available(iOS 14, *) { | |
assertionFailure("Please don't use this ViewModifer directly and use the `onChange(of:perform:)` modifier instead.") | |
} | |
return content | |
.onAppear() | |
.onReceive(Just(newValue)) { newValue in | |
if let (currentValue, action) = state, newValue != currentValue { | |
action(newValue) | |
} | |
state = (newValue, newAction) | |
} | |
} | |
} | |
extension View { | |
@_disfavoredOverload | |
@ViewBuilder public func onChange<V>(of value: V, perform action: @escaping (V) -> Void) -> some View where V: Equatable { | |
if #available(iOS 14, *) { | |
onChange(of: value, perform: action) | |
} else { | |
modifier(ChangeObserver(newValue: value, action: action)) | |
} | |
} | |
} |
Interesting, I'll definitely re-test then as I'm 99% (was 100% hahaha) certain this didn't behave this way for me. I'd be super happy to be wrong here mind you 👍
Ok I can see my issue. I thought it was a shadowed property but actually that wasn't it.
Essentially I've also ported the .task(id:)
modifier on top of this. However it was there that the task id
change was triggering correctly, however the value appeared to always be the previous value.
I've now identified its because in my TaskModifier
implementation, I had passed my id
in as a @State
property which was a mistake. Removing that property wrapper/annotation now fixed my issue.
So the onChange
implementation (both yours and mine) were not at all at fault. My mistake 👍
What is the purpose of the onAppear()
on line 22?
Also i'm getting [SwiftUI] Modifying state during view update, this will cause undefined behavior.
on line 27, which is fixable by adding .receive(on: DispatchQueue.main)
operator to the Just
on line 13
-> .onReceive(Just(newValue).receive(on: DispatchQueue.main)) { newValue in
+1 To what is the purpose of onAppear
@KoCMoHaBTa @kuanfajardo I don't remember well but it was probably a workaround to some bug.
This is such a beautiful solution...... wow......
Great solution! Saved me. Just one question: What's the idea of putting action
into state
? Seems, the action never changes within the struct lifecycle
@subtranix my backports library actually has some improvements and is far more battle tested against a lot more projects id suggest checking that out.
@shaps80 I just tried it in a sample project and it seems to work as I described it.
If you're able to setup a sample project showcasing your issue, I'd be happy to help you.