У меня есть приложение SwiftUI, которое использует пролистывание для удаления списка пользователей. Моя проблема в том, что когда я это делаю, случайный элемент массива выбирается и удаляется.
Моей первой мыслью было, что индекс был назначен ошибочно, но, просматривая код, я думаю, что функция индекса работает правильно. Похоже что-то в выборе строки.
Есть предположения?
Вот мой код:
import SwiftUI
struct AdminUserTableView: View {
@State var userViewModel = UserViewModel.shared
@State var deleteAdminUserMessage = ""
@State var deleteAdminAlert = false
@State var deleteConfirm = false
@State var getUsersMessage = ""
@State var getUsersAlert = false
var body: some View {
Text("")
.navigationTitle("Admin Users")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
NavigationLink
{
AddUserView()
} label: {
Text("+")
.font(.largeTitle)
}
}
}
List {
ForEach(userViewModel.adminUsers, id: \.self) { user in
AdminUserCellView(user: user)
.swipeActions {
Button("Delete") {
deleteConfirm = true
}
.tint(.red)
}
.confirmationDialog("Are you sure you want to delete \(user.email)?", isPresented: $deleteConfirm, titleVisibility: .visible) {
Button("Delete", role: .destructive) {
Task {
do {
//Delete user from server
var httpStatus = try await NetworkController.shared.deleteAdminUser(user: user)
let index = UserViewModel.shared.adminUsers.firstIndex(where: { $0.id == user.id })!
//Delete user from array.
userViewModel.removeUser(at: index)
} catch {
deleteAdminUserMessage = "Unable to delete user. Please try again."
deleteAdminAlert = true
}
}
}
Button("Cancel", role: .cancel) {}
}
}
}
.alert(deleteAdminUserMessage, isPresented: $deleteAdminAlert) {
Button("OK", role: .cancel) { }
}
.onAppear {
Task {
do {
UserViewModel.shared.adminUsers = try await NetworkController.shared.getAdminUsers()
} catch {
getUsersMessage = "Failed to get admin users from server. Go back, then try again."
getUsersAlert = true
}
}
}
}
}
Вот UserViewModel:
import Foundation
@Observable class UserViewModel {
static var shared = UserViewModel()
var adminUsers: [User] = []
var residentUsers: [ResidentUser] = []
func addAdminUser(_ user: User) {
adminUsers.append(user)
}
func removeUser(at index: Int) {
adminUsers.remove(at: index)
}
}
Отредактировано, чтобы добавить: id: .self в forEach. Это ничего не изменило.
Я нашел потенциальное частичное решение - привязать диалог подтверждения к пользователю. Проблема остается в том, что он все еще не удаляет нужного пользователя. Но это решает половину проблемы. Вот код:
import SwiftUI
struct AdminUserTableView: View {
@State private var userViewModel = UserViewModel()
@State var deleteAdminUserMessage = ""
@State var deleteAdminAlert = false
// @State var deleteConfirm = false
@State var userToDelete: User? = nil
@State var getUsersMessage = ""
@State var getUsersAlert = false
var body: some View {
Text("")
.navigationTitle("Admin Users")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
NavigationLink
{
AddUserView()
} label: {
Text("+")
.font(.largeTitle)
}
}
}
List {
ForEach(userViewModel.adminUsers, id: \.self) { user in
AdminUserCellView(user: user)
.swipeActions {
Button("Delete") {
userToDelete = user
}
.tint(.red)
}
.confirmationDialog("Are you sure you want to delete \(userToDelete?.email ?? "")?", isPresented: Binding<Bool>(
get: { userToDelete != nil },
set: { if !$0 { userToDelete = nil } }
), titleVisibility: .visible) {
Button("Delete", role: .destructive) {
if let userToDelete = userToDelete {
Task {
do {
var httpStatus = try await NetworkController.shared.deleteAdminUser(user: user)
if httpStatus.statusCode == 200 {
let index = userViewModel.adminUsers.firstIndex(where: { $0.id == user.id })!
userViewModel.removeUser(at: index)
}
} catch {
deleteAdminUserMessage = "Unable to delete user. Please try again."
deleteAdminAlert = true
}
}
}
}
Button("Cancel", role: .cancel) {}
}
}
}
.alert(deleteAdminUserMessage, isPresented: $deleteAdminAlert) {
Button("OK", role: .cancel) { }
}
.onAppear {
Task {
do {
userViewModel.adminUsers = try await NetworkController.shared.getAdminUsers()
} catch {
getUsersMessage = "Failed to get admin users from server. Go back, then try again."
getUsersAlert = true
}
}
}
}
}
Я отредактировал, чтобы добавить UserViewModel. Он объявлен Observable, поэтому я не могу сделать userViewModel StateObject. Я попробовал использовать let index = userViewModel.adminUsers.firstIndex(where: {$0.id == user.id })! без изменений. Я отредактировал диалог подтверждения, чтобы показать, какой адрес электронной почты выбирается. Каждый раз он показывает другое электронное письмо, казалось бы, случайно.
Я бы не рекомендовал использовать здесь шаблон синглтон, т.е. удалять static var shared = UserViewModel(). Объявите @State private var userViewModel = UserViewModel() и используйте userViewModel непосредственно в своем коде вместо UserViewModel.shared..... Попробуйте это и посмотрите, будет ли это иметь значение.
Я внес предложенные изменения без изменений. Диалог подтверждения по-прежнему каждый раз показывает случайного пользователя.





Попробуйте этот подход, используя одну модель достоверных данных, @State private var userViewModel = UserViewModel(),
и посвященный @State private var selected: User?
обеспечить корректное удаление нужного пользователя.
Пример кода:
struct ContentView: View {
var body: some View {
NavigationStack {
AdminUserTableView()
}
}
}
struct AdminUserTableView: View {
@State private var userViewModel = UserViewModel() // <--- here
@State private var deleteAdminUserMessage = ""
@State private var deleteAdminAlert = false
@State private var deleteConfirm = false
@State private var getUsersMessage = ""
@State private var getUsersAlert = false
@State private var selected: User? // <--- here
var body: some View {
Text("xxxxx") // for my testing
.navigationTitle("Admin Users")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
NavigationLink
{
// AddUserView()
Text("AddUserView")
} label: {
Text("+")
.font(.largeTitle)
}
}
}
List {
ForEach(userViewModel.adminUsers) { user in
// AdminUserCellView(user: user)
Text(user.name)
.swipeActions {
Button("Delete") {
selected = user // <--- here
deleteConfirm = true
}
.tint(.red)
}
.confirmationDialog("Are you sure you want to delete \(selected?.name ?? "not valid")?", isPresented: $deleteConfirm, titleVisibility: .visible) {
Button("Delete", role: .destructive) {
Task {
do {
if let selected { // <--- here
//Delete user from server
//var httpStatus = try await NetworkController.shared.deleteAdminUser(user: selected)
userViewModel.deleteAdminUser(selected)
}
} catch {
deleteAdminUserMessage = "Unable to delete user. Please try again."
deleteAdminAlert = true
}
}
}
Button("Cancel", role: .cancel) {}
}
}
}
.alert(deleteAdminUserMessage, isPresented: $deleteAdminAlert) {
Button("OK", role: .cancel) { }
}
.task { // <--- here
do {
// userViewModel.adminUsers = try await NetworkController.shared.getAdminUsers()
// for my testing
userViewModel.adminUsers = [User(name: "user-0"),User(name: "user-1"),User(name: "user-2"),User(name: "user-3"),User(name: "user-4"),User(name: "user-5")]
} catch {
getUsersMessage = "Failed to get admin users from server. Go back, then try again."
getUsersAlert = true
}
}
}
}
@Observable class UserViewModel {
var adminUsers: [User] = []
var residentUsers: [ResidentUser] = []
func deleteAdminUser(_ user: User) {
if let index = adminUsers.firstIndex(where: { $0.id == user.id }) {
adminUsers.remove(at: index)
}
}
}
struct User: Identifiable {
let id = UUID()
var name: String
init(name: String) {
self.name = name
}
}
struct ResidentUser: Identifiable {
let id = UUID()
var name: String
init(name: String) {
self.name = name
}
}
Если мой ответ помог, примите его, отметив галочку рядом с ним, она станет зеленой.
Мой код в итоге немного отличался от вашего, но вы помогли мне найти ответ, так что спасибо.
Перейдите по этой ссылке Управление данными модели в вашем приложении , чтобы узнать, как управлять данными в вашем приложении. Поскольку вы не показываете код своего
UserViewModel, возможно, его нужно объявить как@StateObject, если этоclass UserViewModel: ObservableObject.... Вы пробовали использоватьlet index = userViewModel.adminUsers.firstIndex(where: { $0.id == user.id })!. Покажите минимальный воспроизводимый код, который создает вашу проблему, см.: минимальный код