Проведение списка SwiftUI для удаления выбирает случайный элемент

У меня есть приложение 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, возможно, его нужно объявить как @StateObject, если это class UserViewModel: ObservableObject.... Вы пробовали использовать let index = userViewModel.adminUsers.firstIndex(where: { $0.id == user.id })!. Покажите минимальный воспроизводимый код, который создает вашу проблему, см.: минимальный код

workingdog support Ukraine 26.05.2024 08:44

Я отредактировал, чтобы добавить UserViewModel. Он объявлен Observable, поэтому я не могу сделать userViewModel StateObject. Я попробовал использовать let index = userViewModel.adminUsers.firstIndex(where: {$0.id == user.id })! без изменений. Я отредактировал диалог подтверждения, чтобы показать, какой адрес электронной почты выбирается. Каждый раз он показывает другое электронное письмо, казалось бы, случайно.

AFinch 26.05.2024 08:58

Я бы не рекомендовал использовать здесь шаблон синглтон, т.е. удалять static var shared = UserViewModel(). Объявите @State private var userViewModel = UserViewModel() и используйте userViewModel непосредственно в своем коде вместо UserViewModel.shared..... Попробуйте это и посмотрите, будет ли это иметь значение.

workingdog support Ukraine 26.05.2024 09:04

Я внес предложенные изменения без изменений. Диалог подтверждения по-прежнему каждый раз показывает случайного пользователя.

AFinch 26.05.2024 09:13
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
56
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Попробуйте этот подход, используя одну модель достоверных данных, @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
    }
}

Если мой ответ помог, примите его, отметив галочку рядом с ним, она станет зеленой.

workingdog support Ukraine 26.05.2024 10:42

Мой код в итоге немного отличался от вашего, но вы помогли мне найти ответ, так что спасибо.

AFinch 26.05.2024 18:21

Другие вопросы по теме