如何从另一个变量观察一个变量?

How to observe a variable from another variable?

我有一个名为 items 的数组存储在 Clients class 中,它符合 ObservableObject 协议。像这样:

class Clients: ObservableObject {
    @Published var items = [ClientItem]() {
        didSet {
            if let encoded = try? JSONEncoder().encode(items) {
                UserDefaults.standard.set(encoded, forKey: "Clients")
            }
        }
    }
    
    init() {
        if let savedClients = UserDefaults.standard.data(forKey: "Clients") {
            if let decodedClients = try? JSONDecoder().decode([ClientItem].self, from: savedClients) {
                items = decodedClients
                return
            }
        }
        
        items = []
    }
}

然后,在 ContentView 结构中,我有以下内容:

struct ContentView: View {
    @StateObject var clients = Clients()
    @State private var showAddClient = false
    @State private var showFilterSheet = false
    
    var body: some View {
        NavigationView {
            List {
                ForEach(???) { item in
                    HStack {
                        VStack(alignment: .leading) {
                            Text(item.name).font(.headline)
                            Text(item.id)
                            Text(item.isVisited ? "Visited" : "Not visited")
                        }
                        
                        Spacer()
                        
                        VStack(alignment: .trailing) {
                            Link(item.phone, destination: URL(string: "tel:\(item.phone)")!)
                            Text(item.email)
                        }
                    }
                }
                .onDelete(perform: removeItems)
            }
            .navigationTitle("Dummy")
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    Button("Filter") {
                        showFilterSheet = true
                    }
                    .confirmationDialog("Select filter", isPresented: $showFilterSheet) {
                        Button("ID") {
                            ??? = clients.items.sorted { (client1, client2) -> Bool in
                                let clientId1 = client1.id
                                let clientId2 = client2.id
                                return (clientId1.localizedCaseInsensitiveCompare(clientId2) == .orderedAscending)
                            }
                            print(clients.items)
                        }
                        Button("Name") {
                            ??? = clients.items.sorted { (client1, client2) -> Bool in
                                let clientName1 = client1.name
                                let clientName2 = client2.name
                                return (clientName1.localizedCaseInsensitiveCompare(clientName2) == .orderedAscending)
                            }
                        }
                        Button("Visited") {
                            ??? = clients.items.filter { item in
                                return item.isVisited == true
                            }
                        }
                        Button("No visitados") {
                            ??? = clients.items.filter { item in
                                return item.isVisited == false
                            }
                        }
                        Button("Cancel", role: .cancel) { }
                    } message: {
                        Text("Filter by")
                    }
                }
                
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button {
                        showAddClient = true
                    } label: {
                        Image(systemName: "plus")
                    }
                }
            }
            .sheet(isPresented: $showAddClient) {
                AddView(clients: clients)
            }
        }
    }
    
    func removeItems(at offsets: IndexSet) {
        clients.items.remove(atOffsets: offsets)
    }
}

如您所见,我想过滤客户端数组,但是,如果我这样做,那么我将修改存储的客户端,这是我想避免的事情。请注意代码中的 ??? 片段,这些片段引用了可以解决问题的可能变量。这样的变量将观察 clients.items 属性 以能够显示创建的新项目,以及在不覆盖现有数据的情况下过滤它们。

注意:我知道将此类数据存储在 UserDefaults 中不是一个好的做法,但我这样做是为了简单起见。我在这里的主要重点是了解如何处理可观察对象。


编辑ClientItem

struct ClientItem: Identifiable, Codable { 让ID:字符串 让名字:字符串 让 phone: 字符串 让电子邮件:字符串 var isVisited = false }

我对过滤采取了不同的方法。我已将以下内容添加到 Clients class:

var itemsOrderedByName: [ClientItem] {
    items.sorted { (client1, client2) -> Bool in
        let client1 = client1.name
        let client2 = client2.name
        return (client1.localizedCaseInsensitiveCompare(client2) == .orderedAscending)
    }
}
    
var itemsOrderedById: [ClientItem] {
    items.sorted { (client1, client2) -> Bool in
        let client1 = client1.id
        let client2 = client2.id
        return (client1.localizedCaseInsensitiveCompare(client2) == .orderedAscending)
    }
}
    
var itemsVisited: [ClientItem] {
    items.filter { item in
        return item.isVisited == true
    }
}
    
var itemsNotVisited: [ClientItem] {
    items.filter { item in
        return item.isVisited == false
    }
}

虽然我仍然不知道在 ForEach(???) 行中放什么。

如评论中所述,您可以将数组设为计算 属性。结果数组将是您在 ForEach.

中显示的内容

请参阅内联评论以获取解释。

class Clients: ObservableObject {
    @Published var items : [ClientItem] = [
        .init(id: "1", name: "Z", phone: "1", email: "c"),
        .init(id: "2", name: "Y", phone: "2", email: "b"),
        .init(id: "3", name: "X", phone: "3", email: "a", isVisited: true)
    ]
    
    enum FilterType {
        case none, id, name, visited, notVisited
    }
    
    @Published var filterType : FilterType = .none //what type of filter is active
    
    var itemsOrderedByName: [ClientItem] {
        items.sorted { (client1, client2) -> Bool in
            let client1 = client1.name
            let client2 = client2.name
            return (client1.localizedCaseInsensitiveCompare(client2) == .orderedAscending)
        }
    }
        
    var itemsOrderedById: [ClientItem] {
        items.sorted { (client1, client2) -> Bool in
            let client1 = client1.id
            let client2 = client2.id
            return (client1.localizedCaseInsensitiveCompare(client2) == .orderedAscending)
        }
    }
        
    var itemsVisited: [ClientItem] {
        items.filter { item in
            return item.isVisited == true
        }
    }
        
    var itemsNotVisited: [ClientItem] {
        items.filter { item in
            return item.isVisited == false
        }
    }
    
    var filteredItems : [ClientItem] { //return an array based on the current filter type
        switch filterType {
        case .none:
            return items
        case .id:
            return itemsOrderedById
        case .name:
            return itemsOrderedByName
        case .visited:
            return itemsVisited
        case .notVisited:
            return itemsNotVisited
        }
    }
}

struct ContentView: View {
    @StateObject var clients = Clients()
    @State private var showAddClient = false
    @State private var showFilterSheet = false
    
    var body: some View {
        NavigationView {
            List {
                ForEach(clients.filteredItems) { item in //iterate over the filtered elements
                    HStack {
                        VStack(alignment: .leading) {
                            Text(item.name).font(.headline)
                            Text(item.id)
                            Text(item.isVisited ? "Visited" : "Not visited")
                        }
                        
                        Spacer()
                        
                        VStack(alignment: .trailing) {
                            Link(item.phone, destination: URL(string: "tel:\(item.phone)")!)
                            Text(item.email)
                        }
                    }
                }
                .onDelete(perform: removeItems)
            }
            .navigationTitle("Dummy")
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    Button("Filter") {
                        showFilterSheet = true
                    }
                    .confirmationDialog("Select filter", isPresented: $showFilterSheet) {
                        Button("ID") {
                            clients.filterType = .id //set the filter type (same for the other types of buttons)
                        }
                        Button("Name") {
                            clients.filterType = .name
                        }
                        Button("Visited") {
                            clients.filterType = .visited
                        }
                        Button("No visitados") {
                            clients.filterType = .notVisited
                        }
                        Button("Cancel", role: .cancel) {
                            clients.filterType = .none
                        }
                    } message: {
                        Text("Filter by")
                    }
                }
                
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button {
                        showAddClient = true
                    } label: {
                        Image(systemName: "plus")
                    }
                }
            }
        }
    }
    
    func removeItems(at offsets: IndexSet) {
        //we can't just use the index any more, because of the filter. So, find the corresponding item based on the current filter and then delete the item from the original array with a matching id
        if let offset = offsets.first {
            let item = clients.filteredItems[offset]
            clients.items.removeAll { [=10=].id == item.id }
        }
    }
}