'Get `AttributeGraph: cycle detected` error when changing disabled state of text field

When I update the isDisabled state variable in my view, it updates the .disabled modifier of my text field as expected, but it then causes about 40 instances of the following error to appear in the console (with varying attribute numbers at the end): === AttributeGraph: cycle detected through attribute 200472 ===

And then it says: AttributeGraphError[59460:4808136] [SwiftUI] Modifying state during view update, this will cause undefined behavior.

Here is a minimal version of the code producing the error:

struct ContentView: View {
  @State var isDisabled = false
  @State var text = ""
  
  var body: some View {
    VStack {
      TextField("", text: $text)
        .textFieldStyle(.roundedBorder)
        .disabled(isDisabled)

      Button("Disable text field") { isDisabled = true }
    }
  }
}

How do I fix this error?



Solution 1:[1]

After a couple hours of painful debugging, I figured out the solution!

It turns out the problem was that you can't disable the text field while the user is still editing the field. Instead, you must first resign the text field (i.e. close the keyboard), and then disable the text field.

Here's my updated code:

struct ContentView: View {
  @State var isDisabled = false
  @State var text = ""
  
  var body: some View {
    VStack {
      TextField("", text: $text)
        .textFieldStyle(.roundedBorder)
        .disabled(isDisabled)

      Button("Disable text field") {
        closeKeyboard()
        isDisabled = true
      }
    }
  }

  func closeKeyboard() {
    UIApplication.shared.sendAction(
      #selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil
    )
  }
}

Solution 2:[2]

Here is an iOS 15+ solution.

struct ContentView: View {
    
    enum Field: Hashable {
        case text
    }
    @FocusState private var focusedField: Field?   // available iOS 15+
    
    @State var isDisabled = false
    @State var text = ""
    
    var body: some View {
        VStack {
            TextField("", text: $text)
                .focused($focusedField, equals: .text)
                .textFieldStyle(.roundedBorder)
                .disabled(isDisabled)
            
            Button("Disable text field") {
                focusedField = nil
                isDisabled = true
            }
        }
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 wristbands
Solution 2 tricarb