1

I have a view that I want to disable a view wile the network is busy. I have the following observable object that has state depending on the what is going on with the network. This is in SwiftUI 2.0 and Mac BigSur.

class FeedModel: ObservableObject {
    enum CurrenState: Equatable {
        case none
        case loading
        case error
        case done
    }

   private (set) var feed: RssFeed?
   @Published private (set) var currentState: CurrenState = .none

   ... network code.
}

My view has the following code:

struct FeedAddressSheet: View {
    @State var text: String = ""
    @Binding var isOpen: Bool
    @ObservedObject var model = FeedModel()

    public var body: some View {
        ZStack {
          VStack(alignment:.leading) {
              Text("Enter a feed address")
              TextField("", text: $text)
                .frame(idealWidth: 300)
              HStack {
                Spacer()
                Button("Cancel") {
                  isOpen = false
                }
                .keyboardShortcut(.cancelAction)
                Button("Add") {
                    model.load(address: text)
                }
                .keyboardShortcut(.defaultAction)
             }
           }
          .padding()
          .disabled(model.currentState == .loading)
          
          if model.currentState == .loading {
             ProgressView()
              .progressViewStyle(CircularProgressViewStyle())
          }
     }
     .alert(isPresented: Binding.constant(model.currentState == .error)) {
          Alert(title: Text("error"))
      }    
   }
}

The line where I have .disabled(model.currentState == .loading) is causing the following error.

=== AttributeGraph: cycle detected through attribute 247128 ===
=== AttributeGraph: cycle detected through attribute 242488 ===
=== AttributeGraph: cycle detected through attribute 249240 ===
=== AttributeGraph: cycle detected through attribute 249240 ===
=== AttributeGraph: cycle detected through attribute 250280 ===
=== AttributeGraph: cycle detected through attribute 250280 ===
=== AttributeGraph: cycle detected through attribute 242488 ===
=== AttributeGraph: cycle detected through attribute 252824 ===

I am not sure why I am getting a cycle.

So this appears to be an issue with the text field and being bound to a state var.. The following code is shows the issue.

```struct ContentView: View {
@State var enabled = true
@State var text = ""

var body: some View {
    VStack {
        TextField("", text: $text)
        .padding()
        .disabled(!enabled)
        Button("Press me") {
            enabled.toggle()
        }
    }
}

}

Scott Andrew
  • 41
  • 1
  • 6
  • Read this Apple Developer Forum post titled [AttributeGraph: cycle detected](https://developer.apple.com/forums/thread/126890) in particular the two responses by Jim Dovey. That may help your understanding of what is occurring. – andrewbuilder Sep 08 '20 at 00:31
  • This might be helpful https://stackoverflow.com/a/63018486/12299030. – Asperi Sep 08 '20 at 04:25
  • There is no way to see this that I can see in instruments or in the call stack. The call stack has none of my code. It has to do with changing the disabled flag on the view. Not matter what view. – Scott Andrew Sep 08 '20 at 21:40
  • @ScottAndrew I ran into the same issue and traced it down to conditionally showing the `ProgressView`, just like you did in your code snippet, like so: ` if model.currentState == .loading { ProgressView() .progressViewStyle(CircularProgressViewStyle()) } ` – Ugo Apr 28 '21 at 19:10

1 Answers1

0

I believe you're having the same problem I had. To fix it, you must first resign the text field as the first responder, and then disable it:

struct ContentView: View {
@State var enabled = true
@State var text = ""

var body: some View {
    VStack {
        TextField("", text: $text)
        .padding()
        .disabled(!enabled)
        Button("Press me") {
            closeKeyboard()
            enabled.toggle()
        }
    }
}
func closeKeyboard() {
    UIApplication.shared.sendAction(
        #selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil
    )
}
}

Here's the stackoverflow article I wrote on the subject.

wristbands
  • 1,021
  • 11
  • 22