Reputation: 63487
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 ===
Upvotes: 34
Views: 14894
Reputation: 1909
In my case it was an issue with looping define maxSize
property in custom ViewModefier
. It appears that the maxSize
property be causing frequent updates. So pay attention to using GeometryReader!
func body(content: Content) -> some View {
if locked {
ZStack {
content
.background(
GeometryReader { geo in
Color.clear
.onAppear {
self.maxSize = geo
}
}
)
Rectangle()
.contentShape(Rectangle())
.foregroundStyle(.clear)
.frame(width: maxSize?.size.width ?? 0,
height: maxSize?.size.height ?? 0)
.onTapGesture {
show = true
}
}
}
}
Upvotes: 0
Reputation: 1385
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.)
Upvotes: 0
Reputation: 1460
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.
Upvotes: 0
Reputation: 258285
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
and, well, how much it could be helpful depends on your scenario, but definitely you'll get error generated stack in Xcode.
Upvotes: 70
Reputation: 1389
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.
Upvotes: 16
Reputation: 1191
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.
Upvotes: 2
Reputation: 1323
@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
}
}
}
Upvotes: 0
Reputation: 713
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>
) {}
}
Upvotes: 0