24

I'm getting a lot of AttributeGraph cycle warnings in my app that uses SwiftUI. Is there any way to debug what's causing it?

This is what shows up in the console:

=== AttributeGraph: cycle detected through attribute 11640 ===
=== AttributeGraph: cycle detected through attribute 14168 ===
=== AttributeGraph: cycle detected through attribute 14168 ===
=== AttributeGraph: cycle detected through attribute 44568 ===
=== AttributeGraph: cycle detected through attribute 3608 ===
Asperi
  • 228,894
  • 20
  • 464
  • 690
Sindre Sorhus
  • 62,972
  • 39
  • 168
  • 232
  • What version of Xcode are you using? I had this issue happen in Xcode 11.4 and it miraculously resolved it self in later versions. – Andrew Jul 13 '20 at 06:14
  • Happens in both Xcode 11.4 and 12 beta 2. – Sindre Sorhus Jul 13 '20 at 08:12
  • I just tried mine in Xcode 12 Beta 2 and I don't get it for my code, trying it in 11.4 the issue is still there. I wonder if it is just an Xcode issue...are you able to create a reproducible example that you could share with Apple as a [feedback](https://feedbackassistant.apple.com/)? My issue was caused by setting a view in a UIViewRepresentable to be hidden. – Andrew Jul 13 '20 at 08:31
  • I can probably create a reproducible example, but the reason I'm asking here is because I'm looking for a better way than "just removing stuff until it works" kinda debugging. – Sindre Sorhus Jul 13 '20 at 10:22
  • That kind of debugging does suck. I am sorry I don't have a better solution for you. I would be interesting in finding out if there is a way to debug the attribute graph, cause it took me a while to figure out what was causing it. – Andrew Jul 13 '20 at 10:25
  • 1
    Would you add some demo code to get this issue, `cause I did not meet it for a long time? – Asperi Jul 18 '20 at 09:29
  • So what is causing this issue? – Artur Marchetto Oct 24 '20 at 16:11

7 Answers7

53

The log is generated by (from private AttributeGraph.framework)

AG::Graph::print_cycle(unsigned int) const ()

so you can set symbolic breakpoint for print_cycle

demo

and, well, how much it could be helpful depends on your scenario, but definitely you'll get error generated stack in Xcode.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • That is very useful. Thanks! How did you find out where the warning message was generated from? – Sindre Sorhus Jul 22 '20 at 15:59
  • 3
    `$ nm /System/Library/PrivateFrameworks/AttributeGraph.framework/AttributeGraph` – Asperi Jul 22 '20 at 16:01
  • How do I get from the breakpoint to information I can do something with? What is attribute %u ===\n"? All of this is situated well ahead of [NSApplication run] and I cannot work out what the offending attribute might be. The documentation I could find is somewhat sparse and only says 'you can set breakpoints'. – green_knight Sep 25 '22 at 19:14
14

For me this issue was caused by me disabling a text field while the user was still editing it.

To fix this, you must first resign the text field as the first responder (thus stopping editing), and then disable the text field. I explain this more in this Stack Overflow answer.

Duncan Babbage
  • 19,972
  • 4
  • 56
  • 93
wristbands
  • 1,021
  • 11
  • 22
1

For me, this issue was caused by trying to focus a TextField right before changing to the tab of a TabView containing the TextField.

It was fixed by simply focusing the TextField after changing the TabView tab.

This seems similar to what @wristbands was experiencing.

Martin
  • 1,112
  • 1
  • 11
  • 31
0

For me the issue was resolved by not using UIActivityIndicator... not sure why though. The component below was causing problems.

public struct UIActivityIndicator: UIViewRepresentable {
   
   private let style: UIActivityIndicatorView.Style
   
   /// Default iOS 11 Activity Indicator.
   public init(
      style: UIActivityIndicatorView.Style = .large
   ) {
      self.style = style
   }
   
   public func makeUIView(
      context: UIViewRepresentableContext<UIActivityIndicator>
   ) -> UIActivityIndicatorView {
      return UIActivityIndicatorView(style: style)
   }
   
   public func updateUIView(
      _ uiView: UIActivityIndicatorView,
      context: UIViewRepresentableContext<UIActivityIndicator>
   ) {}
}
Artur Marchetto
  • 646
  • 1
  • 5
  • 17
0

@Asperi Here is a minimal example to reproduce AttributeGraph cycle:

import SwiftUI

struct BoomView: View {
    var body: some View {
        VStack {
            Text("Go back to see \"AttributeGraph: cycle detected through attribute\"")
                .font(.title)
            Spacer()
        }
    }
}


struct TestView: View {
    @State var text: String = ""
    @State private var isSearchFieldFocused: Bool = false
    
    var placeholderText = NSLocalizedString("Search", comment: "")
    
    var body: some View {
        NavigationView {
            VStack {
                FocusableTextField(text: $text, isFirstResponder: $isSearchFieldFocused, placeholder: placeholderText)
                    .foregroundColor(.primary)
                    .font(.body)
                    .fixedSize(horizontal: false, vertical: true)
                
                NavigationLink(destination: BoomView()) {
                    Text("Boom")
                }
                
                Spacer()
            }
            .onAppear {
                self.isSearchFieldFocused = true
            }
            .onDisappear {
                isSearchFieldFocused = false
            }
        }
    }
}

FocusableTextField.swift based on https://stackoverflow.com/a/59059359/659389

import SwiftUI

struct FocusableTextField: UIViewRepresentable {
    @Binding public var isFirstResponder: Bool
    @Binding public var text: String
    var placeholder: String = ""

    public var configuration = { (view: UITextField) in }

    public init(text: Binding<String>, isFirstResponder: Binding<Bool>, placeholder: String = "", configuration: @escaping (UITextField) -> () = { _ in }) {
        self.configuration = configuration
        self._text = text
        self._isFirstResponder = isFirstResponder
        self.placeholder = placeholder
    }

    public func makeUIView(context: Context) -> UITextField {
        let view = UITextField()
        view.placeholder = placeholder
        view.autocapitalizationType = .none
        view.autocorrectionType = .no
        view.addTarget(context.coordinator, action: #selector(Coordinator.textViewDidChange), for: .editingChanged)
        view.delegate = context.coordinator
        return view
    }

    public func updateUIView(_ uiView: UITextField, context: Context) {
        uiView.text = text
        switch isFirstResponder {
        case true: uiView.becomeFirstResponder()
        case false: uiView.resignFirstResponder()
        }
    }

    public func makeCoordinator() -> Coordinator {
        Coordinator($text, isFirstResponder: $isFirstResponder)
    }

    public class Coordinator: NSObject, UITextFieldDelegate {
        var text: Binding<String>
        var isFirstResponder: Binding<Bool>

        init(_ text: Binding<String>, isFirstResponder: Binding<Bool>) {
            self.text = text
            self.isFirstResponder = isFirstResponder
        }

        @objc public func textViewDidChange(_ textField: UITextField) {
            self.text.wrappedValue = textField.text ?? ""
        }

        public func textFieldDidBeginEditing(_ textField: UITextField) {
            self.isFirstResponder.wrappedValue = true
        }

        public func textFieldDidEndEditing(_ textField: UITextField) {
            self.isFirstResponder.wrappedValue = false
        }
    }
}
koleS
  • 1,263
  • 6
  • 30
  • 46
  • 3
    Can you explain how this answers the question? This does not look like an answer but an example that produces an AttributedGraph cycle. – Andrew Dec 05 '21 at 22:04
0

For me the issue was that I was dynamically loading the AppIcon asset from the main bundle. See this Stack Overflow answer for in-depth details.

Roger Oba
  • 1,292
  • 14
  • 28
0

I was using enum cases as tag values in a TabView on MacOS. The last case (of four) triggered three attributeGraph cycle warnings. (The others were fine). I am now using an Int variable (InspectorType.book.typeInt instead of InspectorType.book) as my selection variable and the cycle warnings have vanished.

(I can demonstrate this by commenting out the offending line respectively by changing the type of my selection; I cannot repeat it in another app, so there's obviously something else involved; I just haven't been able to identify the other culprit yet.)

green_knight
  • 1,319
  • 14
  • 26