Duck
Duck

Reputation: 36003

Old and New value of an Observable Object in .onChange

I have an observable object called MyObject that has a published property called property. property is String.

I have this

.onChange(of:myObject.property, perform: { value in
}

the problem is that value contains the old value of property, when this is triggered.

Apple docs say you can capture the new and old value of properties by doing this:

@State private var playState: PlayState = .paused


.onChange(of: playState) { [playState] newState in
    print(playState, self.playState, newState)
}

I don't see how I should type the command for my case.

I have tried

.onChange(of:myObject.property, perform: { [myObject.property] newValue in

but Xcode says

Expected 'weak', 'unowned', or no specifier in capture list

Any ideas?

Upvotes: 8

Views: 4139

Answers (3)

mw_906
mw_906

Reputation: 278

I ran into the same issue. You can capture the old value of you model property by assigning it inside the capture brackets.

.onChange(of:myObject.property, perform: { [oldValue = myObject.property] newValue in
  print("Old value was \(oldValue), new value is \(newValue)")   
}

Upvotes: 18

swiftPunk
swiftPunk

Reputation: 1

New Update Version 2.0.0

I decided separate this 2 way, because it would make confusing of usage or using wrong code for wrong way.

Notice that you can use in this way as well, I used other way in body:

.performOnChange(of: model.randomElement, withKey: "WhiteRabbit") { value in
    
    print("oldValue:", value.oldValue)
    print("newValue:", value.newValue)
    print("- - - - - - -")
    
}

import SwiftUI

struct ContentView: View {
    
    @StateObject var model: Model = Model()
    
    var body: some View {
        
        VStack {
            
            Text(model.randomElement)
                .padding()
            
            Button("shuffle") { model.randomElement = model.array[Int.random(in: model.array.indices)] }
            
        }
        .font(Font.headline)
        .performOnChange(of: model.randomElement, withKey: "WhiteRabbit") { oldValue, newValue in  // <<: Here! using: performOnChange
            
            print("oldValue:", oldValue)
            print("newValue:", newValue)
            print("- - - - - - -")
            
        }

    }
}


class Model: ObservableObject {
    
    let array: [String] = ["a", "b", "c", "d"]
    
    @Published var randomElement: String = "No random Element!"
    
}

extension View {
    
    func performOnChange<T: Equatable>(of inPutValue: T, withKey key: String, capturedValues: @escaping (ReturnValueType<T>) -> Void) -> some View {
        
        return self
            .preference(key: OptionalGenericPreferenceKey<T>.self, value: inPutValue)
            .onPreferenceChange(OptionalGenericPreferenceKey<T>.self) { newValue in
                
                if let unwrappedNewValue: T = newValue {
                    
                    if let unwrappedOldValue: T = backupDictionary[key] as? T { capturedValues((oldValue: unwrappedOldValue, newValue: unwrappedNewValue)) }
                    
                }
                
                backupDictionary[key] = newValue
                
            }
        
    }
    
}


typealias ReturnValueType<T: Equatable> = (oldValue: T, newValue: T)

var backupDictionary: [String: Any] = [String: Any]()

struct OptionalGenericPreferenceKey<T: Equatable>: PreferenceKey {
    
    static var defaultValue: T? { get { return nil } }
    
    static func reduce(value: inout T?, nextValue: () -> T?) { value = nextValue() }
    
}

Upvotes: 3

swiftPunk
swiftPunk

Reputation: 1

Version 1.0.0

Here a working example of it, notice that onChange would work if there is a change in value, otherwise will no react:


import SwiftUI

struct ContentView: View {
    
    @StateObject var model: Model = Model()
    @State private var randomElement: String = Model().randomElement
    
    var body: some View {
        
        VStack {
            
            Text(model.randomElement)
                .padding()
            
            Button("shuffle") { model.randomElement = model.array[Int.random(in: model.array.indices)] }
            
        }
        .font(Font.headline)
        .onChange(of: model.randomElement) { newValue in randomElement = newValue }
        .onChange(of: randomElement) { [randomElement] newValue in
            
            print("oldValue is:", randomElement)
            print("newValue is:", newValue)
            print("- - - - - - - - - - - - ")
            
        }
        
        
    }
}

class Model: ObservableObject {
    
    let array: [String] = ["a", "b", "c", "d"]
    
    @Published var randomElement: String = "No random Element!"
    
}

Upvotes: 1

Related Questions