Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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))
}
}
}
@jegnux

This comment has been minimized.

Copy link
Owner Author

@jegnux jegnux commented Apr 15, 2021

This version of onChange(of:perform:) is a full iOS 13 compatible drop-in replacement of iOS 14's eponym modifier.

Thanks to @_disfavoredOverload this method will be used in favor of native modifier only where the iOS 13 compatibility is required. This is why in the if #available(iOS 14, *) I can safely call the original method without causing a recursive loop.

@bennokress

This comment has been minimized.

Copy link

@bennokress bennokress commented Apr 16, 2021

Nice solution. One thing you might want to add: Just in line 22 requires import Combine, otherwise it won't build.

@jegnux

This comment has been minimized.

Copy link
Owner Author

@jegnux jegnux commented Apr 21, 2021

@bennokress thanks, it's done :)

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