Search code examples
iosswiftuicombineproperty-wrapper

Custom property wrapper not accurately reflecting the state of my TextField in SwiftUI, any idea why?


I've created a property wrapper that I want to insert some logic into and the "set" value is doing the right thing, but the textfield isn't updating with all uppercase text. Shouldn't the text field be showing all uppercase text or am I misunderstanding how this is working?

Also this is a contrived example, my end goal is to insert a lot more logic into a property wrapper, I'm just using the uppercase example to get it working. I've searched all over the internet and haven't found a working solution.

import SwiftUI
import Combine

struct ContentView: View {
    @StateObject var vm = FormDataViewModel()

    var body: some View {
        Form {
            TextField("Name", text: $vm.name)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

class FormDataViewModel: ObservableObject {
    @Capitalized var name: String = ""
}

@propertyWrapper
public class Capitalized {
    @Published var value: String

    public var wrappedValue: String {
        get { value }
        set { value = newValue.uppercased() } //Printing this shows all caps
    }

    public var projectedValue: AnyPublisher<String, Never> {
        return $value
            .eraseToAnyPublisher()
    }

    public init(wrappedValue: String) {
        value = wrappedValue
    }
}

Solution

  • SwiftUI watches @Published properties in @StateObject or @ObservedObject and triggers UI update on changes of them.

    But it does not go deep inside the ObservableObject. Your FormDataViewModel does not have any @Published properties.


    One thing you can do is that simulating what @Published will do on value changes.

    class FormDataViewModel: ObservableObject {
        @Capitalized var name: String = ""
        private var nameObserver: AnyCancellable?
    
        init() {
            nameObserver = _name.$value.sink {_ in
                self.objectWillChange.send()
            }
        }
    }
    

    Please try.