首页 > 解决方案 > SwiftUI NavigationLink 在删除然后插入新的 ForEach 元素后中断

问题描述

出于某种原因,我的 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
                        }

但这似乎是一个巨大的黑客攻击。

标签: swiftuiswiftui-listswiftui-navigationlinkswiftui-foreach

解决方案


推荐阅读