如何使用 SwiftUI 重置子视图状态变量?
Posted
技术标签:
【中文标题】如何使用 SwiftUI 重置子视图状态变量?【英文标题】:How to reset child view state variable with SwiftUI? 【发布时间】:2020-07-21 10:36:04 【问题描述】:我确定这很愚蠢,但是当另一个状态发生变化时,应该如何重置子视图的状态值?
例如下面的代码显示了2个文件夹,分别有2个和3个项目,可以编辑。
如果您选择第二个文件夹 (Work) 及其第三个项目 (Peter),然后选择第一个文件夹 (Home),则应用程序会崩溃,因为 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<[Item]>)
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<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)
【问题讨论】:
这能回答你的问题吗? Handling derived state in SwiftUI 我尝试将您的代码粘贴到 Xcode 11.4 中,但出现了很多编译器错误,因此很难进行试验。 IndexedCollection 上应该有类型参数吗?有些东西可能没有成为您粘贴到问题中的内容。例如,我看到Binding<[Item]>
变成了Binding
。
【参考方案1】:
感谢 @jordanpittman 提出修复建议:
ItemsView(items: $store.data[selectedFolderIndex!].items).id(selectedRowIndex)
来源:https://swiftui-lab.com/swiftui-id
【讨论】:
【参考方案2】: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())
【讨论】:
以上是关于如何使用 SwiftUI 重置子视图状态变量?的主要内容,如果未能解决你的问题,请参考以下文章