Skip to content

Instantly share code, notes, and snippets.

@jegnux
Last active August 1, 2024 15:04
Show Gist options
  • Save jegnux/c3aee7957f6c372bf31a46c893a6e2a2 to your computer and use it in GitHub Desktop.
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))
}
}
}
@heecheon92
Copy link

This is such a beautiful solution...... wow......

@subtranix
Copy link

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

@shaps80
Copy link

shaps80 commented Nov 10, 2023

@subtranix my backports library actually has some improvements and is far more battle tested against a lot more projects id suggest checking that out.

https://github.com/shaps80/SwiftUIBackports/blob/main/Sources/SwiftUIBackports/Shared/OnChange/OnChange.swift

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment