1

I'm using .onReceive(_:perform:) to add pattern masks in my text field text. There is an infinite loop that happens when I use array of ObservableObjects, Binding, State objects or Published properties.

Example

struct MyTextField: View {
    @Binding var text: String
    
    ...
}

extension MyTextField {
    func mask(_ mask: String) -> some View {
        self.onReceive(Just(text)) { newValue in

            // `string(withMask:)` returns another string (already formatted correctly)
            text = newValue.string(withMask: mask) // Obviously when I comment this line of code, the problem stops
        }
    }
}

Usage

final class FormItem: ObservableObject, Identifiable {
    let id = UUID()
    
    @Published var text = ""
    let mask: String
    
    init(mask: String) {
        self.mask = mask
    }
}
@State var volunteerForm: [FormItem] = [
    FormItem(mask: "999.999.999-99")
]


var body: some View {
   VStack {
      ForEach(volunteerForm.indices, id: \.self) { index in
         MyTextField("", text: volunteerForm[index].$text, onCommit: onCommit)
            .mask(volunteerForm[index].mask)
      }
   }

}

But when I use a single property just like this @State var formItem: FormItem = ... this infinite loop doesn't happen. Also when I use an array of String instead of array of my Class FormItem, @State var volunteerTexts: [String] = [""], it doesn't happen too.

I wonder if this happen when we use a custom struct or class.

I've tried creating the model without ObservableObject and Published, just like a struct, but the infinite loop keeps happening:

struct FormItem: Identifiable {
    let id = UUID()
    
    var text = ""
    let mask: String
}
VStack {
   ForEach(volunteerForm.indices, id: \.self) { index in
      TextField("", text: $volunteerForm[index].text, onCommit: onCommit)
         .mask(volunteerForm[index].mask)
   }
}

Do you have any ideia why is this infinite loop occurring?

  • I don't think you need the `.onReceive` there at all. If the text changes, the entire body that hosts the `MyTextField` view would be recomputed, so the you'd get the updated text. But the infinite loop happens because the `.onReceive` would always get a value from a `Just` publisher, and would always change `text`, which would cause the entire thing to be recomputed, starting the cycle again – New Dev Oct 11 '20 at 21:36
  • @NewDev Thank you for your answer! I used `onReceive` because we cannot implement a code like `self._text = text.string(withMask: mask)` in `var body: some View` , which throws an error: **Cannot assign to property: 'self' is immutable**, when adding `_text = text.string(withMask: "999.999.999-99")` inside a `Group` or **Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type** outside the `Group` – Thiago Centurion Oct 11 '20 at 22:02
  • Not directly in the body... but you could do that in `.onAppear` or something. You could also check that if there is not change, then skip the assignment – New Dev Oct 11 '20 at 22:05
  • Thanks for alternative. I just tried using `onAppear` instead of `onReceive(Just(text))`, but although view updates when the `text` is changed, `onAppear` appears to be called only once. – Thiago Centurion Oct 11 '20 at 22:10
  • Also I just tried using `onEditingChanged: (Bool) -> Void` alternative to update the applying mask, but it appears to be called only when the TextField becomes and resign first responder, but it isn't called when the texts changed – Thiago Centurion Oct 11 '20 at 22:19
  • That's a whole other issue of updating the text as it is being typed. See if this helps: https://stackoverflow.com/a/59509242/968155 – New Dev Oct 11 '20 at 22:31
  • I have tried that just now and it worked, it stop the infinite loop! However, this solution makes the new text field representable to not conform for changing SwiftUI functions like: `.font(.system(size: 14, weight: .bold, design: .rounded)).foregroundColor(Color.Style.grayLight)` and etc. I know that I can inject all needed properties, but is there any way to use that SwiftUI functions? – Thiago Centurion Oct 12 '20 at 00:23
  • I don't know.. there might not be, but this is a good candidate for a separate SO question – New Dev Oct 12 '20 at 01:05
  • @NewDev Thank you! I found this example to create fake view builders functions: https://forums.swift.org/t/creating-flexible-uiviewrepresentable-types/31388 – Thiago Centurion Oct 12 '20 at 02:32

0 Answers0