SwiftUI NavigationLink прерывается после удаления, а затем вставки нового элемента ForEach

#swiftui #swiftui-list #swiftui-navigationlink #swiftui-foreach

#swiftui #swiftui-список #swiftui-navigationlink #swiftui-для каждого

Вопрос:

По какой-то причине моя NavigationLink прерывается при определенных обстоятельствах:

Учитывая приведенный ниже код, вот шаги для воспроизведения:

  • Нажмите Войти, который вставит учетную запись в список
  • Нажмите назад, чтобы открыть стек
  • Проведите пальцем влево и удалите, что приведет к удалению первого элемента списка
  • Нажмите снова войти (должен нажать на стек, но не нажимает)
  • Коснитесь первой строки (она должна попасть в стек, но не попадает)

Вот код:

 import SwiftUI

class Account: ObservableObject, Identifiable, Equatable, Hashable {
    let id: String
    
    init(id: String) {
        self.id = id
    }
    
    static func == (lhs: Account, rhs: Account) -> Bool {
        return lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

class AccountManager: ObservableObject {
    @Published private (set) var isLoading: Bool = false
    @Published private (set) var accounts: [Account] = []
    
    init() {
        load()
    }
    
    func load() {
        isLoading = true
        DispatchQueue.main.asyncAfter(deadline: .now()   .milliseconds(500)) {
            self.accounts = [ Account(id: UUID().uuidString) ]
            self.isLoading = false
        }
    }
    
    func add(account: Account) {
        accounts.insert(account, at: 0)
    }
    
    func delete(at offsets: IndexSet) {
        accounts.remove(atOffsets: offsets)
    }
}

struct AccountManagerEnvironmentKey: EnvironmentKey {
    static var defaultValue: AccountManager = AccountManager()
}

extension EnvironmentValues {
    var accountManager: AccountManager {
        get { return self[AccountManagerEnvironmentKey.self] }
        set { self[AccountManagerEnvironmentKey.self] = newValue }
    }
}

struct ContentView: View {
    @Environment(.accountManager) var accountManager
    
    @State var isLoading: Bool = false
    @State var accounts: [Account] = []
    @State var selectedAccount: Account? = nil
    
    var body: some View {
        NavigationView() {
            ZStack {
                List {
                    ForEach(accounts) { account in
                        NavigationLink(
                            destination: Text(account.id),
                            tag: account,
                            selection: $selectedAccount
                        ) {
                            Text(account.id)
                        }
                    }
                    .onDelete(perform: { offsets in
                        accountManager.delete(at: offsets)
                    })
                }
                if isLoading {
                    ProgressView("Loading...")
                }
            }
            .navigationBarTitle("Accounts", displayMode: .inline)
            .toolbar(content: {
                ToolbarItem(placement: .primaryAction) {
                    Button("Sign In") {
                        let newAccount = Account(id: UUID().uuidString)
                        accountManager.add(account: newAccount)
                        selectedAccount = newAccount
                    }
                }
            })
            .onReceive(accountManager.$isLoading) { value in
                isLoading = value
            }
            .onReceive(accountManager.$accounts) { value in
                accounts = value
            }
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}
 

Если я изменю действие кнопки, чтобы сделать это, это сработает:

                         accountManager.add(account: newAccount)
                        DispatchQueue.main.asyncAfter(deadline: .now()   .milliseconds(100)) {
                            selectedAccount = newAccount
                        }
 

Но это похоже на массовый взлом.

Комментарии:

1. Я не уверен, почему в вашем представлении массив Accounts является переменной состояния. Можете ли вы использовать массив @Published Accounts в своей модели представления (AccountManager), чтобы даже не использовать переменную состояния?

2. @PeterF. Изменение AccountManager на an @ObservedObject и ссылка на него @Published приводят к той же ошибке.

3. Это работает : ForEach(0..<accounts.count, id: .self) { index in let account = accounts[index] ... . Однако это не кажется идеальным, и я не понимаю, почему в этом случае это будет вести себя по-другому.