如何从另一个变量观察一个变量?
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 }
}
}
}
我有一个名为 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 }
}
}
}