6

I have the following code:

struct Credential: Equatable {
    var username = ""
    var password = ""
}

enum Database {
    static var credential = Credential()
}

struct UsernamePasswordInput: View {
    @Binding var credential: Credential

    var body: some View {
        Group {
            TextField("Username", text: self.$credential.username)
            SecureField("password", text: self.$credential.password)
        }
        .textFieldStyle(RoundedBorderTextFieldStyle())
    }
}

struct Login: View {
    private var credential: Binding<Credential>

    var body: some View {
        UsernamePasswordInput(credential: self.credential)
    }

    init() {
        self.credential = Binding<Credential>(
            get: {
                Database.credential
            }, set: { newValue in
                Database.credential = newValue
            }
        )
    }
}

struct ContentView: View {
    var body: some View {
        Login()
    }
}

When you run this code from Xcode 13.0 on an iOS 15 device, the message Binding<String> action tried to update multiple times per frame. appears in the Xcode console. This happens whenever you type a character into either the TextField or the SecureField. The message does not appear on iOS 13 or 14 devices.

I know I can solve it by just using a @StateObject but the above code reflects the structure of a larger project. My question is: is the Xcode message somehow indicative of a problem? Is the code above principally incorrect somehow?

Bart van Kuik
  • 4,704
  • 1
  • 33
  • 57
  • It is likely that custom init. SwiftUI does not do well with those. It can decide to reload the entire View whenever it wants. Instead of a custom binding just use the regular binding. It seems like an oxymoron to have a private variable hacky updated by the outside. – lorem ipsum Oct 06 '21 at 10:35
  • @loremipsum FYI: I moved the creating of the binding in the Login view to the instantiation of the UsernamePasswordInput view, and that didn't remove the message. – Bart van Kuik Oct 06 '21 at 13:36

1 Answers1

5

Here is a possible safe workaround - use inline separated binding for each text field:

struct UsernamePasswordInput: View {
    @Binding var credential: Credential

    var body: some View {
        Group {
            TextField("Username", text: Binding(
                get: { credential.username },
                set: { credential.username = $0 }
                ))
            SecureField("password", text: Binding(
                get: { credential.password },
                set: { credential.password = $0 }
                ))
        }
        .textFieldStyle(RoundedBorderTextFieldStyle())
    }
}

Tested with Xcode 13 / iOS 15

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Yup, that was it. Obviously, the difference is that my code binds to a struct, and your code binds to a string of that struct. Can you explain why it was wrong to bind to a struct outside the view? – Bart van Kuik Oct 06 '21 at 12:44
  • I had my text fields inside a UIViewControllerRepresentable and I was getting the same warnings in console even though i wasn't binding to a struct but a single value. Your solution worked for me too. I'm also interested as to why it was a problem before : ) – grenos Apr 09 '22 at 13:41