OnChangeModifier.swift 1.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. extension View {
  2. /// A view modifier that runs an action whenever a piece of state changes.
  3. ///
  4. /// - Parameters:
  5. /// - value: The value to observe for changes. Must be `Equatable`.
  6. /// - initial: Whether to call `action` when the view first appears.
  7. /// - action: The action to perform.
  8. public func onChange<Value: Equatable>(
  9. of value: Value,
  10. initial: Bool = false,
  11. perform action: @escaping () -> Void
  12. ) -> some View {
  13. OnChangeModifier(
  14. body: TupleView1(self),
  15. value: value,
  16. action: action,
  17. initial: initial
  18. )
  19. }
  20. }
  21. struct OnChangeModifier<Value: Equatable, Content: View>: View {
  22. // TODO: This probably doesn't have to trigger view updates. We're only
  23. // really using @State here to persist the data.
  24. @State var previousValue: Value?
  25. var body: TupleView1<Content>
  26. var value: Value
  27. var action: () -> Void
  28. var initial: Bool
  29. // TODO: Should this go in computeLayout or commit?
  30. func computeLayout<Backend: BaseAppBackend>(
  31. _ widget: Backend.Widget,
  32. children: any ViewGraphNodeChildren,
  33. proposedSize: ProposedViewSize,
  34. environment: EnvironmentValues,
  35. backend: Backend
  36. ) -> ViewLayoutResult {
  37. if let previousValue, value != previousValue {
  38. action()
  39. } else if initial, previousValue == nil {
  40. action()
  41. }
  42. if previousValue != value {
  43. previousValue = value
  44. }
  45. return defaultComputeLayout(
  46. widget,
  47. children: children,
  48. proposedSize: proposedSize,
  49. environment: environment,
  50. backend: backend
  51. )
  52. }
  53. }