From a74139acfacd35cb199595835815f0acf3a0bf60 Mon Sep 17 00:00:00 2001 From: Qijia Liu Date: Sun, 15 Dec 2024 15:10:08 -0500 Subject: [PATCH] ListView: refactor vm; fix crash on delete; scroll on addition --- src/config/ListView.swift | 110 +++++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/src/config/ListView.swift b/src/config/ListView.swift index 79dca2c..c483930 100644 --- a/src/config/ListView.swift +++ b/src/config/ListView.swift @@ -1,40 +1,20 @@ import SwiftUI -private func deserialize(_ value: Any) -> [Any] { +private struct ListItem: Identifiable { + let id = UUID() + var value: Any +} + +private func deserialize(_ value: Any) -> [ListItem] { guard let value = value as? [String: Any] else { return [] } - return (0.. [String: Any] { +private func serialize(_ value: [ListItem]) -> [String: Any] { return value.enumerated().reduce(into: [String: Any]()) { result, pair in - result[String(pair.offset)] = pair.element - } -} - -private struct ListSectionHeader: View { - let children: Any? - @Binding var value: Any - - var body: some View { - HStack { - Spacer() - Button { - var list = deserialize(value) - if let children = children as? [[String: Any]] { - list.append( - children.reduce(into: [:]) { result, child in - result[child["Option"] as! String] = child["DefaultValue"] ?? "" - } as NSDictionary) - } else { - list.append("") - } - value = serialize(list) - } label: { - Image(systemName: "plus") - } - } + result[String(pair.offset)] = pair.element.value } } @@ -42,35 +22,65 @@ struct ListSubView: OptionViewProtocol { let label: String let data: [String: Any] @Binding var value: Any + @State private var list = [ListItem]() var body: some View { let type = data["Type"] as! String let optionViewType = toOptionViewType(["Type": String(type.suffix(type.count - "List|".count))]) - var list = deserialize(value) - List { - Section(header: ListSectionHeader(children: data["Children"], value: $value)) { - ForEach(list.indices, id: \.self) { i in - AnyView( - optionViewType.init( - label: "", data: data, // List|Entries need this. - value: Binding( - get: { list[i] }, - set: { - list[i] = $0 - value = serialize(list) + ScrollViewReader { proxy in + List { + Section { + ForEach(Array(zip(list.indices, list)), id: \.1.id) { i, item in + AnyView( + optionViewType.init( + label: "", data: data, // List|Entries need this. + value: Binding( + get: { item.value }, + set: { + list[i] = ListItem(value: $0) + value = serialize(list) + } + )) + ).id(item.id) // For scrolling. + } + .onDelete { offsets in + list.remove(atOffsets: offsets) + value = serialize(list) + } + .onMove { indices, newOffset in + list.move(fromOffsets: indices, toOffset: newOffset) + value = serialize(list) + } + } header: { + HStack { + Spacer() + Button { + if let children = data["Children"] as? [[String: Any]] { + list.append( + ListItem( + value: + children.reduce(into: [:]) { result, child in + result[child["Option"] as! String] = child["DefaultValue"] ?? "" + })) + } else { + list.append(ListItem(value: "")) + } + value = serialize(list) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + withAnimation { + proxy.scrollTo(list.last?.id) } - ))) - } - .onDelete { offsets in - list.remove(atOffsets: offsets) - value = serialize(list) - } - .onMove { indices, newOffset in - list.move(fromOffsets: indices, toOffset: newOffset) - value = serialize(list) + } + } label: { + Image(systemName: "plus") + } + } } } }.navigationTitle(label) + .onAppear { + list = deserialize(value) + } } }