最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

ios - Bad performance with nested value types - Stack Overflow

programmeradmin6浏览0评论

I'm experiencing poor SwiftUI performance due to nested value types triggering redraws in the view hierarchy. Here's an example:

I have data model with nested parent/child/grandchild relationships:

struct Parent: Identifiable, Hashable {
    let id: UUID
    var children = [Child]()
}

struct Child: Identifiable, Hashable {
    let id: UUID
    var children = [Grandchild]()
}

struct Grandchild: Identifiable, Hashable {
    let id: UUID
    var name = ""
}

For each model I have a SettingsView that accepts a binding to the model, and navigates to its own children if any:

struct ParentSettingsView: View {
    @Binding var parent: Parent
    var body: some View {
        List(parent.children) { child in
            NavigationLink(child.id.uuidString, value: child)
        }
        .navigationDestination(for: Child.self) { child in
            let idx = parent.children.firstIndex(where: {$0.id == child.id})!
            ChildSettingsView(child: $parent.children[idx])
        }
    }
}
// ChildSettingsView is basically identical

Finally at the bottom of the stack is GrandchildSettingsView:

struct GrandchildSettingsView: View {
    @Binding var grandchild: Grandchild
    var body: some View {
        TextField("Name", text: $grandchild.name)
    }
}

Typing a name in GrandchildSettingsView causes the entire view hierarchy to redraw with every keystroke, because a mutation to Grandchild is also a mutation to Child, and to Parent, etc. This causes very bad performance, and other side effects.

Migrating the full data model to @Observable reference types technically works, but it leads to other issues when dealing with Sendable, Codable, etc. Using structs is far more lightweight for this application.

How can we improve SwiftUI performance with nested value types like these?

I'm experiencing poor SwiftUI performance due to nested value types triggering redraws in the view hierarchy. Here's an example:

I have data model with nested parent/child/grandchild relationships:

struct Parent: Identifiable, Hashable {
    let id: UUID
    var children = [Child]()
}

struct Child: Identifiable, Hashable {
    let id: UUID
    var children = [Grandchild]()
}

struct Grandchild: Identifiable, Hashable {
    let id: UUID
    var name = ""
}

For each model I have a SettingsView that accepts a binding to the model, and navigates to its own children if any:

struct ParentSettingsView: View {
    @Binding var parent: Parent
    var body: some View {
        List(parent.children) { child in
            NavigationLink(child.id.uuidString, value: child)
        }
        .navigationDestination(for: Child.self) { child in
            let idx = parent.children.firstIndex(where: {$0.id == child.id})!
            ChildSettingsView(child: $parent.children[idx])
        }
    }
}
// ChildSettingsView is basically identical

Finally at the bottom of the stack is GrandchildSettingsView:

struct GrandchildSettingsView: View {
    @Binding var grandchild: Grandchild
    var body: some View {
        TextField("Name", text: $grandchild.name)
    }
}

Typing a name in GrandchildSettingsView causes the entire view hierarchy to redraw with every keystroke, because a mutation to Grandchild is also a mutation to Child, and to Parent, etc. This causes very bad performance, and other side effects.

Migrating the full data model to @Observable reference types technically works, but it leads to other issues when dealing with Sendable, Codable, etc. Using structs is far more lightweight for this application.

How can we improve SwiftUI performance with nested value types like these?

Share Improve this question edited Mar 14 at 22:39 Hundley asked Mar 14 at 21:51 HundleyHundley 3,6075 gold badges29 silver badges48 bronze badges 11
  • Binding does nor work with this type of NavigationDestination – lorem ipsum Commented Mar 14 at 22:10
  • 1 Don't use .indicies in your List – Paulw11 Commented Mar 14 at 22:34
  • @loremipsum Binding does work in this example – Hundley Commented Mar 14 at 22:35
  • Agree that you should get rid of parent.children.indices – timbre timbre Commented Mar 14 at 22:40
  • @Paulw11 you're right, .indices was unnecessary here. Updated with cleaner code - but functionally identical for the problem. – Hundley Commented Mar 14 at 22:40
 |  Show 6 more comments

1 Answer 1

Reset to default 1

Try this approach using a extra @State variable as mentioned by @CouchDeveloper, and a .onAppear and .onDisappear to update the binding grandchild. The GrandchildSettingsView will be refreshed as expected, but not the other Views.

Example code.


struct GrandchildSettingsView: View {
    @Binding var grandchild: Grandchild
    @State private var kid = Grandchild(id: UUID(), name: "")
    // or @State private var kidName = ""  ...
    
    var body: some View {
        let _ = print("-----> body GrandchildSettingsView") // <-- for testing
        TextField("Name", text: $kid.name)
            .onAppear {
                kid = grandchild
            }
            .onDisappear{
                grandchild = kid
            }
    }
}

发布评论

评论列表(0)

  1. 暂无评论