Search code examples
swift

How to updated the needed item inside a nested item in an array?


I am looking to update an item in a nested array of items, for showing the issue I made this example code below, the point of this question is updating nested item and not SwiftUI tricks. i want find an item with knowing its id and after finding it trying to updated. In codes bellow I showed you the example for updating items in first layer, but as long as layers go deeper we cannot guess it can be 2 layer or 10 layer, we do not know. How can I be able to update the item1, item2 and item4?

enter image description here

    import SwiftUI


@main
struct findItem_testApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(ItemModel.shared)
        }
    }
}


    
    struct ContentView: View {
        
        @EnvironmentObject var itemModel: ItemModel
        
        var body: some View {
            
            VStack {
    
                MyView(item: itemModel.items[0])
                MyView(item: itemModel.items[1].nestedItems[0].nestedItems[0])
                MyView(item: itemModel.items[1].nestedItems[0])
                MyView(item: itemModel.items[1])
                MyView(item: itemModel.items[2].nestedItems[0])
                MyView(item: itemModel.items[2])
    
            }
    
        }
    }
    
    struct MyView: View {
        
        let item: ItemType
        
        var body: some View {
    
            Text(item.name)
                .bold()
                .padding()
                .onTapGesture {
                    print(item.name, item.id)
                    // need to update item.name from model
                    ItemModel.shared.updateName(id: item.id, value: item.name + " updated!")
                }
      
        }
    }
    
    
    
    struct ItemType: Identifiable {
    
        let id: UUID
        var name: String
        var nestedItems: [ItemType]
    
        init(name: String, nestedItems: [ItemType]) {
            self.id = UUID()
            self.name = name
            self.nestedItems = nestedItems
        }
        
    }
    
    let item0: ItemType = ItemType(name: "item0", nestedItems: [])
    
    let item1: ItemType = ItemType(name: "item1", nestedItems: [])
    let item2: ItemType = ItemType(name: "item2", nestedItems: [item1])
    let item3: ItemType = ItemType(name: "item3", nestedItems: [item2])
    
    let item4: ItemType = ItemType(name: "item4", nestedItems: [])
    let item5: ItemType = ItemType(name: "item5", nestedItems: [item4])
    
    
    class ItemModel: ObservableObject {
        
        static let shared: ItemModel = ItemModel()
        
        @Published var items: [ItemType] = [ItemType]()
        
        init() {
            self.items = [item0, item3, item5]
        }
    
        func updateName(id: UUID, value: String) {
    
            for i in (0 ..< self.items.count) {
    
                if (self.items[i].id == id) {
                    self.items[i].name = value
                    break
                }
    
            }
     
        }
    
    }

Solution

  • How about a recursive approach:

    import Foundation
    
    struct ItemType: Identifiable {
        let id: UUID
        var name: String
        var nestedItems: [ItemType]
    
        init(name: String, nestedItems: [ItemType] = []) {
            self.id = UUID()
            self.name = name
            self.nestedItems = nestedItems
        }
    }
    
    class ItemModel {
        var items: [ItemType]
    
        init(items: [ItemType]) {
            self.items = items
        }
    
        func updateName(id: UUID, newName: String) {
            updateNameRecursively(id: id, newName: newName, items: &self.items)
        }
    
        private func updateNameRecursively(id: UUID, newName: String, items: inout [ItemType]) {
            for i in 0..<items.count {
                if items[i].id == id {
                    items[i].name = newName
                    return 
                } else {
                    updateNameRecursively(id: id, newName: newName, items: &items[i].nestedItems)
                }
            }
        }
    }
    
    extension ItemModel {
        func prettyPrintItems(items: [ItemType], indentLevel: Int = 0) {
            let indent = String(repeating: "  ", count: indentLevel)
            for item in items {
                print("\(indent)- \(item.name) (id: \(item.id))")
                prettyPrintItems(items: item.nestedItems, indentLevel: indentLevel + 1)
            }
        }
    }
    
    let item0 = ItemType(name: "item0")
    let item1 = ItemType(name: "item1")
    let item2 = ItemType(name: "item2", nestedItems: [item1])
    let item3 = ItemType(name: "item3", nestedItems: [item2])
    let item4 = ItemType(name: "item4")
    let item5 = ItemType(name: "item5", nestedItems: [item4])
    let model = ItemModel(items: [item0, item3, item5])
    print("Before:")
    model.prettyPrintItems(items: model.items)
    model.updateName(id: item1.id, newName: "item1 updated!")
    model.updateName(id: item2.id, newName: "item2 updated!")
    model.updateName(id: item4.id, newName: "item4 updated!")
    print("After:")
    model.prettyPrintItems(items: model.items)
    

    If you would like to use a dictionary instead of an array for the nestedItems:

    import Foundation
    
    struct ItemType: Identifiable {
        let id: UUID
        var name: String
        var nestedItems: [String: ItemType]
    
        init(name: String, nestedItems: [String: ItemType] = [:]) {
            self.id = UUID()
            self.name = name
            self.nestedItems = nestedItems
        }
    }
    
    class ItemModel {
        var items: [String: ItemType]
    
        init(items: [ItemType]) {
            self.items = Dictionary(uniqueKeysWithValues: items.map { ($0.id.uuidString, $0) })
        }
    
        func updateName(id: UUID, newName: String) {
            updateNameRecursively(id: id.uuidString, newName: newName, items: &self.items)
        }
    
        private func updateNameRecursively(id: String, newName: String, items: inout [String: ItemType]) {
            if let item = items[id] {
                items[id]?.name = newName
            } else {
                for key in items.keys {
                    updateNameRecursively(id: id, newName: newName, items: &items[key]!.nestedItems)
                }
            }
        }
    }
    
    extension ItemModel {
        func prettyPrintItems(items: [String: ItemType], indentLevel: Int = 0) {
            let indent = String(repeating: "  ", count: indentLevel)
            for item in items.values {
                print("\(indent)- \(item.name) (id: \(item.id))")
                prettyPrintItems(items: item.nestedItems, indentLevel: indentLevel + 1)
            }
        }
    }
    
    let item0 = ItemType(name: "item0")
    let item1 = ItemType(name: "item1")
    let item2 = ItemType(name: "item2", nestedItems: [item1.id.uuidString: item1])
    let item3 = ItemType(name: "item3", nestedItems: [item2.id.uuidString: item2])
    let item4 = ItemType(name: "item4")
    let item5 = ItemType(name: "item5", nestedItems: [item4.id.uuidString: item4])
    let model = ItemModel(items: [item0, item3, item5])
    print("Before:")
    model.prettyPrintItems(items: model.items)
    model.updateName(id: item1.id, newName: "item1 updated!")
    model.updateName(id: item2.id, newName: "item2 updated!")
    model.updateName(id: item4.id, newName: "item4 updated!")
    print("After:")
    model.prettyPrintItems(items: model.items)
    

    Example Output:

    Before:
    - item0 (id: CE42EA37-3841-4D4C-931D-0611FC80E1F0)
    - item3 (id: 0EADCD5E-C24B-495A-9581-97DEC427B859)
      - item2 (id: 44103D4A-0871-4235-B895-4AD16C3AABB0)
        - item1 (id: 0412FC6B-B5A4-4AFB-8BF4-C4569EBA16E7)
    - item5 (id: 4C396B6A-1FA7-4E7B-AE6C-EE4B10395AB6)
      - item4 (id: 5CA53EA0-D229-429C-812E-A364EE725B89)
    After:
    - item0 (id: CE42EA37-3841-4D4C-931D-0611FC80E1F0)
    - item3 (id: 0EADCD5E-C24B-495A-9581-97DEC427B859)
      - item2 updated! (id: 44103D4A-0871-4235-B895-4AD16C3AABB0)
        - item1 updated! (id: 0412FC6B-B5A4-4AFB-8BF4-C4569EBA16E7)
    - item5 (id: 4C396B6A-1FA7-4E7B-AE6C-EE4B10395AB6)
      - item4 updated! (id: 5CA53EA0-D229-429C-812E-A364EE725B89)