TransWikia.com

How do I debug SwiftUI AttributeGraph cycle warnings?

Stack Overflow Asked by Sindre Sorhus on December 30, 2021

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 ===

5 Answers

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.

Answered by Martin on December 30, 2021

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 stackoverflow article.

Answered by wristbands on December 30, 2021

@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
        }
    }
}

Answered by koleS on December 30, 2021

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>
   ) {}
}

Answered by Artur Marchetto on December 30, 2021

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.

Answered by Asperi on December 30, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP