如何使用SwiftUI重置子视图状态变量?

时间:2020-04-08 21:37:50

标签: swiftui

我确定这很愚蠢,但是当另一种状态改变时,应该如何重置子视图的状态值?

例如,下面的代码显示2个文件夹,分别包含2和3个项目,可以进行编辑。

如果您选择第二个文件夹(工作)和其第三个项目(彼得),然后选择第一个文件夹(家庭),则由于selectedItemIndex超出范围,应用程序将崩溃。

我试图在初始化视图时“重置”状态值,但似乎像这样更改状态会触发“运行时:SwiftUI:在视图更新期间修改状态,这将导致未定义的行为”。警告。

init(items: Binding<[Item]>) {  
    self._items = items  
    self._selectedItemIndex = State(wrappedValue: 0)  
}  

执行此操作的正确方法是什么?谢谢!

这是代码:

AppDelegate.swift


    import Cocoa
    import SwiftUI

    @NSApplicationMain
    class AppDelegate: NSObject, NSApplicationDelegate {

        var window: NSWindow!


        func applicationDidFinishLaunching(_ aNotification: Notification) {
            // Create the SwiftUI view that provides the window contents.
            let store = ItemStore()
            let contentView = ContentView(store: store)

            // Create the window and set the content view. 
            window = NSWindow(
                contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
                styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
                backing: .buffered, defer: false)
            window.center()
            window.setFrameAutosaveName("Main Window")
            window.contentView = NSHostingView(rootView: contentView)
            window.makeKeyAndOrderFront(nil)
        }

        func applicationWillTerminate(_ aNotification: Notification) {
            // Insert code here to tear down your application
        }
    }

ContentView.swift


    import SwiftUI

    final class ItemStore: ObservableObject {
        @Published var data: [Folder] = [Folder(name: "Home",
                                                items: [Item(name: "Mark"), Item(name: "Vincent")]),
                                         Folder(name: "Work",
                                                items:[Item(name: "Joseph"), Item(name: "Phil"), Item(name: "Peter")])]
    }

    struct Folder: Identifiable {
        var id = UUID()
        var name: String
        var items: [Item]
    }

    struct Item: Identifiable {
        static func == (lhs: Item, rhs: Item) -> Bool {
            return true
        }

        var id = UUID()
        var name: String
        var content = Date().description

        init(name: String) {
            self.name = name
        }
    }

    struct ContentView: View {
        @ObservedObject var store: ItemStore

        @State var selectedFolderIndex: Int?

        var body: some View {
            HSplitView {
                // FOLDERS
                List(selection: $selectedFolderIndex) {
                    Section(header: Text("Groups")) {
                        ForEach(store.data.indexed(), id: \.1.id) { index, folder in
                            Text(folder.name).tag(index)
                        }
                    }.collapsible(false)
                }
                .listStyle(SidebarListStyle())

                // ITEMS
                if selectedFolderIndex != nil {
                    ItemsView(items: $store.data[selectedFolderIndex!].items)
                }
            }
            .frame(minWidth: 800, maxWidth: .infinity, maxHeight: .infinity)
        }
    }


    struct ItemsView: View {
        @Binding var items: [Item]
        @State var selectedItemIndex: Int?

        var body: some View {
            HSplitView {
                List(selection: $selectedItemIndex) {
                    ForEach(items.indexed(), id: \.1.id) { index, item in
                        Text(item.name).tag(index)
                    }
                }
                .frame(width: 300)

                if selectedItemIndex != nil {
                    DetailView(item: $items[selectedItemIndex!])
                    .padding()
                    .frame(minWidth: 200, maxHeight: .infinity)
                }
            }
        }

        init(items: Binding) {
            self._items = items
            self._selectedItemIndex = State(wrappedValue: 0)
        }
    }


    struct DetailView: View {
        @Binding var item: Item

        var body: some View {
            VStack {
                TextField("", text: $item.name)
            }
        }
    }

    // Credit: https://swiftwithmajid.com/2019/07/03/managing-data-flow-in-swiftui/

    struct IndexedCollection: RandomAccessCollection {
        typealias Index = Base.Index
        typealias Element = (index: Index, element: Base.Element)

        let base: Base

        var startIndex: Index { base.startIndex }

        var endIndex: Index { base.endIndex }

        func index(after i: Index) -> Index {
            base.index(after: i)
        }

        func index(before i: Index) -> Index {
            base.index(before: i)
        }

        func index(_ i: Index, offsetBy distance: Int) -> Index {
            base.index(i, offsetBy: distance)
        }

        subscript(position: Index) -> Element {
            (index: position, element: base[position])
        }
    }

    extension RandomAccessCollection {
        func indexed() -> IndexedCollection {
            IndexedCollection(base: self)
        }
    }

2 个答案:

答案 0 :(得分:0)

感谢parent-module提出修复建议:

ItemsView(items: $store.data[selectedFolderIndex!].items).id(selectedRowIndex)

来源:@jordanpittman

答案 1 :(得分:0)

ContentView.swift的完全可播放的示例草稿。在两种编辑模式(非活动/活动行选择)中进行播放,并根据需要进行选择。

import SwiftUI

struct ItemStore {
    var data: [Folder] = [Folder(name: "Home", items: [Item(name: "Mark"), Item(name: "Vincent")]),
                          Folder(name: "Work", items:[Item(name: "Joseph"), Item(name: "Phil"), Item(name: "Peter")])]
}

struct Folder: Identifiable {
    var id = UUID()
    var name: String
    var items: [Item]
}

struct Item: Identifiable {

    var id = UUID()
    var name: String
    var content = Date().description

}

struct ContentView: View {
    @State var store: ItemStore

    @State var selectedFolderIndex: Int? = 0
    @State private var editMode = EditMode.inactive
    var body: some View {
        NavigationView {
            VStack {
                // FOLDERS

                List(selection: $selectedFolderIndex) {
                    Section(header: Text("Groups")) {
                        ForEach(store.data.indexed(), id: \.1.id) { index, folder in
                            HStack {
                                Text(folder.name).tag(index)
                                Spacer()
                            }
                                .background(Color.white) //make the whole row tapable, not just the text
                            .frame(maxWidth: .infinity)
                            .multilineTextAlignment(.leading)
                            .onTapGesture {
                                self.selectedFolderIndex = index
                            }
                        }.onDelete(perform: delete)
                    }
                }
                .listStyle(GroupedListStyle())
                .id(selectedFolderIndex)
                // ITEMS
                if selectedFolderIndex != nil && (($store.data.wrappedValue.startIndex..<$store.data.wrappedValue.endIndex).contains(selectedFolderIndex!) ){
                    ItemsView(items: $store.data[selectedFolderIndex!].items)
                }
            }
            .navigationBarTitle("Title")
            .navigationBarItems(trailing: EditButton())
            .environment(\.editMode, $editMode)
        }
    }

    func delete(at offsets: IndexSet) {
        $store.wrappedValue.data.remove(atOffsets: offsets) // Note projected value! `store.data.remove() will not modify SwiftUI on changes and it will crash because of invalid index.
    }
}


struct ItemsView: View {
    @Binding var items: [Item]
    @State var selectedDetailIndex: Int?
    var body: some View {
        HStack {
            List(selection: $selectedDetailIndex) {
                ForEach(items.indexed(), id: \.1.id) { index, item in
                    Text(item.name).tag(index)
                    .onTapGesture {
                        self.selectedDetailIndex = index
                    }
                }

            }

            if selectedDetailIndex != nil && (($items.wrappedValue.startIndex..<$items.wrappedValue.endIndex).contains(selectedDetailIndex!) )    {
                DetailView(item: $items[selectedDetailIndex!])
                    .padding()
            }
        }

    }

}


struct DetailView: View {
    @Binding var item: Item

    var body: some View {
        VStack {
            TextField("", text: $item.name)
        }
    }
}

// Credit: https://swiftwithmajid.com/2019/07/03/managing-data-flow-in-swiftui/

struct IndexedCollection<Base: RandomAccessCollection>: RandomAccessCollection {
    typealias Index = Base.Index
    typealias Element = (index: Index, element: Base.Element)

    let base: Base

    var startIndex: Index { base.startIndex }

    var endIndex: Index { base.endIndex }

    func index(after i: Index) -> Index {
        base.index(after: i)
    }

    func index(before i: Index) -> Index {
        base.index(before: i)
    }

    func index(_ i: Index, offsetBy distance: Int) -> Index {
        base.index(i, offsetBy: distance)
    }

    subscript(position: Index) -> Element {
        (index: position, element: base[position])
    }
}

extension RandomAccessCollection {
    func indexed() -> IndexedCollection<Self> {
        IndexedCollection(base: self)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(store: ItemStore())
    }
}