如何以编程方式从 SwiftUI 列表中删除行并刷新列表视图?
Posted
技术标签:
【中文标题】如何以编程方式从 SwiftUI 列表中删除行并刷新列表视图?【英文标题】:How to programatically delete row from SwiftUI List and have the List view refreshes as well? 【发布时间】:2020-09-13 15:18:49 【问题描述】:我希望以编程方式从 SwiftUI 列表数据源中删除一条记录,当我检测到 onAppear 该列表有一条陈旧的记录时,该记录在用户离开列表屏幕时得到更新。
例如,用户在项目编辑屏幕上。此屏幕标记 Environment 对象中的一个属性,该属性与更新的记录有关,以便 List 屏幕可以使用此属性在 List 再次出现时从 List 数据源中删除过时的记录。希望这种刷新 List 的模式可以吗?
List
Section
Toggle(isOn: self.$showArchivedItems.onChange(showOptionsChanged))
Text("Show Archived Records).font(.headline)
Section
ForEach(userData.items) dataItem in
NavigationLink(destination: ItemDetailView(item: dataItem))
ListRowItemView(item: dataItem)
.onDelete(perform: self.onDeletingItems)
.onAppear()
if let updatedRecord = self.userData.lastUpdatedRecord
if let updatedRecordId = self.userData.items.firstIndex(where: return $0.id == updatedRecord.id)
//OPTION #1 - Doesn’t work
//self.userData.items.remove(at: updatedRecordId)
//self.userData.items.append(updatedRecord)
//OPTION #2 - Doesn’t work
//self.userData.items.replaceSubrange(updatedRecordId... updatedRecordId, with: [updatedRecord])
//OPTION #3 - works
self.userData.items = self.storage.load()
.listStyle(GroupedListStyle())
private func onDeletingItems(indexset: IndexSet)
var itemsToDeactivate: [ItemModel] = []
for index in indexset
userData.items[index].isArchived = true
itemsToDeactivate.append(userData.items[index])
for item in itemsToDeactivate
storage.persist(dataItem: item)
userData.items.remove(atOffsets: indexset)
附加代码:
UserData.swift
import Foundation
final class UserData: ObservableObject
@Published var items: [ItemModel]
var lastUpdatedRecord: ItemModel? = nil
init(data: [ItemModel] = [])
self.items = data
func clearLastUpdatedRecord()
self.lastUpdatedRecord = nil
列表视图
struct ListView: View
@EnvironmentObject var userData: UserData
var body: some View
NavigationView
VStack
List
//ForEach on userData.items
SceneDelegate.swift
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
// Create the SwiftUI view that provides the window contents.
let storage = ItemLocalStorage()
let userData = UserData(data: storage.load())
let userSettings = SettingsModel()
let contentView = ContentView().environmentObject(storage).environmentObject(userData).environmentObject(userSettings)
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
问题是我尝试了两个选项来专门从列表数据源中删除过时的记录,方法是找到它的索引(在选项#1中)并附加更新的实例,或者在选项#2中我试图用更新的替换过时的记录实例。
在这两个选项中,更新的记录确实会添加到列表中,但过时的记录不会从列表中删除,尽管过时的记录已经更新了数据,但我看到了更新记录的双重实例。
当我从存储中完全刷新数据源(选项# 3)时,列表会显示正确的项目,即只有一个更新记录的实例。
任何人都可以建议如何以编程方式从列表数据源中删除记录,以便列表也刷新其视图。我的意思是我不必再次从存储中加载所有记录来更新列表,因为单个记录已更新。 (在我的应用程序中,更新是用户触发的,一次只能更新一条记录,根据当前设计)
我还想知道,List 确实支持编辑 List 中的项目,就像我在 List 上实现了 onDelete 一样。在这里,用户可以将 List 置于 editMode 并删除单个项目。我确信 List 没有调用我的存储来提取所有数据。那么 List 是如何管理删除单个项目并刷新其显示的呢?
【问题讨论】:
只需从userData.items
中删除元素
我已经在 OPTION#1 中这样做了,但它没有从列表显示中删除。列表继续显示项目
你会显示userData
类型的代码吗?
添加了 userData 的代码和其他一些代码
您的 ItemModel
类型是否有唯一的 id 字段?喜欢UUID
?
【参考方案1】:
通过将 List 的数据源捕获为显式状态变量,我设法找到了该问题的解决方法。如果我现在对此状态变量进行更改,即删除并添加行,则列表显示会反映更新的记录。
假设 SwiftUI 最喜欢 @State 以在底层值更改时自动呈现 UI。因此,我没有处理 EnvironmentObject 中保存的 Array 的副本,而是将工作传递给了一个专用的 @State 变量。在此之后,我得到了想要的行为。
我的主要变化是
ListView.swift
@State private var items: [ItemModel] = []
init(items: [ItemModel])
_items = State(initialValue: items)
List
Section
Toggle(isOn: self.$showArchivedItems.onChange(showOptionsChanged))
Text("Show Archived”).font(.headline)
Section
ForEach(self.items) item in
NavigationLink(destination: ItemDetailView(item: item))
ListRowItemView(item: item)
.onDelete(perform: self.onDeletingItems)
.onAppear()
if let updatedRow = self.userData.lastUpdatedRecord
self.updateListDataSource(updatedRow)
.listStyle(GroupedListStyle())
fileprivate func updateListDataSource(_ updatedRow: ItemModel)
if let updatedRecordId = self.items.firstIndex(where: return $0.id == updatedRow.id)
self.items.remove(at: updatedRecordId)
self.items.append(updatedRow)
self.userData.clearLastUpdatedRecord()
private func onDeletingItems(indexset: IndexSet)
var itemsToDeactivate: [ItemModel] = []
for index in indexset
userData.items[index].isArchived = true
userData.items[index].lastModified = Date()
itemsToDeactivate.append(userData.items[index])
for item in itemsToDeactivate
storage.persist(item: item)
self.userData.items.removeAll $0.id == item.id
self.items.remove(atOffsets: indexset)
UserData.swift
import Foundation
final class UserData: ObservableObject
@Published var items: [ItemModel]
var lastUpdatedRecord: ItemModel? = nil
didSet
if let updatedInstance = lastUpdatedRecord
if let updatedInstanceIndex = self.items.firstIndex(where: $0.id == updatedInstance.id)
self.items.remove(at: updatedInstanceIndex)
self.items.append(updatedInstance)
init(data: [ItemModel] = [])
self.items = data
func clearLastUpdatedRecord()
self.lastUpdatedRecord = nil
【讨论】:
以上是关于如何以编程方式从 SwiftUI 列表中删除行并刷新列表视图?的主要内容,如果未能解决你的问题,请参考以下文章
如何以编程方式从 Android 的默认应用列表中删除应用?