How to access parent in List View with children?

I have a simple List with nested children forming a tree. Each item can be checked. If all children are checked (or not) then the parent must reflect this state. It must also be possible to change the state of the children by selecting the parent.

Below is a simple example that sort-of works. When you change the state of a parent, the state of all the children change as well. But how do I make it work the other way around so that the parent is notified when a child changes? When all the children are checked, the parent should be checked.

I cannot get a reference to the parent. I tried adding handlers but that captures self (the parent) in an escaping closure that mutates it.

I created a timer for each item that would periodically check the children but this just feels very wrong.

I do not want to convert all the value-type coding to reference types but I cannot find an elegant way of solving this problem. Any suggestions will be greatly appreciated.

import SwiftUI

struct Item: Identifiable {
  var id = UUID()
  var name: String
  var isSelected: Bool = false {
    didSet {
      guard children != nil else { return }
      for i in 0..<children!.count {
        children![i].isSelected = isSelected
      }
    }
  }
  
  var children: [Item]?
}

struct ContentView: View {
  @Binding var itemsTree: [Item]
  
  var body: some View {
    List($itemsTree, children: \.children) { $item in
      Toggle(item.name, isOn: $item.isSelected)
    }
  }
}

@main
struct ListQuestionApp: App {
  @State private var itemTree: [Item] = [
    Item(name: "Level 1", children: [
      Item(name: "Level 2", children: [
        Item(name: "Item B"),
        Item(name: "Item A")
      ])
    ])
  ]
  
  var body: some Scene {
    WindowGroup {
      ContentView(itemsTree: $itemTree)
    }
  }
}

Accepted Reply

You really only have 2 choices here:

  1. Store the parent's id in each child. Since Items are Identifiable, the id is stable for the parent, so this is safe to do. You'll also need to make sure you replace the id in the child when it changes parents.

  2. Walk the tree from the root to the child, looking for the parent that has the child in its children array. This is safe to do if children can have only one parent, which I assume is true here. There's probably no significant performance penalty for doing this, since the toggling is being done at human UI speeds.

Both of those can be a parent property on the child — a stored property for #1, or a computed property for #2.

Replies

You really only have 2 choices here:

  1. Store the parent's id in each child. Since Items are Identifiable, the id is stable for the parent, so this is safe to do. You'll also need to make sure you replace the id in the child when it changes parents.

  2. Walk the tree from the root to the child, looking for the parent that has the child in its children array. This is safe to do if children can have only one parent, which I assume is true here. There's probably no significant performance penalty for doing this, since the toggling is being done at human UI speeds.

Both of those can be a parent property on the child — a stored property for #1, or a computed property for #2.

Thanks, I never considered using the ID as reference.