保持原始数据源与下游绑定同步

huangapple go评论61阅读模式
英文:

Keeping the original datasource in sync with downstream bindings

问题

更改FruitDetailView上的项目到一个绑定不会更改原始数据源。

英文:

If I have a collection of fruits, and I pass one of them to a detail view, how do I edit that item so that both the item and it's original datasource are updated?

final class Merchant: ObservableObject {
    
    @Published
    var selection: Fruit?
    
    @Published
    var fruits = [
        Fruit(name: "Banana"),
        Fruit(name: "Apple")
    ]
}

struct FruitsView: View {
    
    @EnvironmentObject var merchant: Merchant
            
    var body: some View {
        VStack {
            ForEach(merchant.fruits) { fruit in
                Button {
                    merchant.selection = fruit
                } label: {
                    Text(fruit.name)
                }
                .buttonStyle(.borderedProminent)
            }
        }
        .sheet(item: $merchant.selection, content: {
            FruitDetailView(item: $0)
        })
    }
}

struct FruitDetailView: View {

    let item: Fruit
    
    init(item: Fruit) {
        self.item = item
    }
    
    var body: some View {
        VStack {
            Text(item.name)
            Button("Press Me") {
                item.name = "Watermelon" // error
            }
        }
    }
}

Changing the item on FruitDetailView to a binding doesn't change the original datasource.

答案1

得分: 0

有几种方法可以实现你的要求。这是一个使用你已有的模型构建的简单方法之一。它使用Merchantselection来更新Merchantfruits数据。

struct ContentView: View {
    @StateObject var merchant = Merchant()
    
    var body: some View {
        FruitsView().environmentObject(merchant)
    }
}

struct Fruit: Identifiable {
    let id = UUID()
    var name: String
}

final class Merchant: ObservableObject {
    
    @Published var selection: Fruit? = nil {
        didSet {
            if selection != nil,
               let index = fruits.firstIndex(where: {$0.id == selection!.id}) {
                fruits[index].name = selection!.name
            }
        }
    }
    
    @Published var fruits = [Fruit(name: "Banana"), Fruit(name: "Apple")]
}

struct FruitsView: View {
    @EnvironmentObject var merchant: Merchant
    
    var body: some View {
        VStack {
            ForEach(merchant.fruits) { fruit in
                Button {
                    merchant.selection = fruit
                } label: {
                    Text(fruit.name)
                }.buttonStyle(.borderedProminent)
            }
        }
        .sheet(item: $merchant.selection) { _ in
            FruitDetailView().environmentObject(merchant)
        }
    }
}

struct FruitDetailView: View {
    @EnvironmentObject var merchant: Merchant

    var body: some View {
        VStack {
            Text(merchant.selection?.name ?? "no selection name")
            Button("Press Me") {
                merchant.selection?.name = "Watermelon"
            }
        }
    }
}

EDIT-1:

这是另一种保持模型“同步”的方法。它使用了Merchant的ObservableObject类中的updateFruits函数来更新模型的数据。

它使用一个本地的@State var selection: Fruit?将UI交互部分与Merchant模型的主要数据分离开来。

final class Merchant: ObservableObject {
    @Published var fruits = [Fruit(name: "Banana"), Fruit(name: "Apple")]
    
    func updateFruits(with item: Fruit) {
        if let index = fruits.firstIndex(where: {$0.id == item.id}) {
            fruits[index].name = item.name
        }
    }
}

struct FruitsView: View {
    @EnvironmentObject var merchant: Merchant
    @State var selection: Fruit?
    
    var body: some View {
        VStack {
            ForEach(merchant.fruits) { fruit in
                Button {
                    selection = fruit
                } label: {
                    Text(fruit.name)
                }.buttonStyle(.borderedProminent)
            }
        }
        .sheet(item: $selection) { item in
            FruitDetailView(item: item).environmentObject(merchant)
        }
    }
}

struct FruitDetailView: View {
    @EnvironmentObject var merchant: Merchant
    @State var item: Fruit

    var body: some View {
        VStack {
            Text(item.name)
            Button("Press Me") {
                item.name = "Watermelon"
                merchant.updateFruits(with: item)
            }
        }
    }
}
英文:

There are a number of ways to achieve what you ask. This is one simple way using the model constructs you already have. It uses the Merchant selection to update the Merchant fruits data.

struct ContentView: View {
    @StateObject var merchant = Merchant()
    
    var body: some View {
        FruitsView().environmentObject(merchant)
    }
}

struct Fruit: Identifiable {
    let id = UUID()
    var name: String
}

final class Merchant: ObservableObject {
    
    @Published var selection: Fruit? = nil {
        didSet {
            if selection != nil,
               let index = fruits.firstIndex(where: {$0.id == selection!.id}) {
                fruits[index].name = selection!.name
            }
        }
    }
    
    @Published var fruits = [Fruit(name: "Banana"), Fruit(name: "Apple")]
}

struct FruitsView: View {
    @EnvironmentObject var merchant: Merchant
    
    var body: some View {
        VStack {
            ForEach(merchant.fruits) { fruit in
                Button {
                    merchant.selection = fruit
                } label: {
                    Text(fruit.name)
                }.buttonStyle(.borderedProminent)
            }
        }
        .sheet(item: $merchant.selection) { _ in
            FruitDetailView().environmentObject(merchant)
        }
    }
}

struct FruitDetailView: View {
    @EnvironmentObject var merchant: Merchant

    var body: some View {
        VStack {
            Text(merchant.selection?.name ?? "no selection name")
            Button("Press Me") {
                merchant.selection?.name = "Watermelon"
            }
        }
    }
}

EDIT-1:

This is another way of keeping the model in sync. It uses a function updateFruits in the Merchant ObservableObject class, to update the model's data.

It separates the UI interaction part using a local @State var selection: Fruit? from the main data in the Merchant model.

final class Merchant: ObservableObject {
    @Published var fruits = [Fruit(name: "Banana"), Fruit(name: "Apple")]
    
    func updateFruits(with item: Fruit) {
        if let index = fruits.firstIndex(where: {$0.id == item.id}) {
            fruits[index].name = item.name
        }
    }
}

struct FruitsView: View {
    @EnvironmentObject var merchant: Merchant
    @State var selection: Fruit?
    
    var body: some View {
        VStack {
            ForEach(merchant.fruits) { fruit in
                Button {
                    selection = fruit
                } label: {
                    Text(fruit.name)
                }.buttonStyle(.borderedProminent)
            }
        }
        .sheet(item: $selection) { item in
            FruitDetailView(item: item).environmentObject(merchant)
        }
    }
}

struct FruitDetailView: View {
    @EnvironmentObject var merchant: Merchant
    @State var item: Fruit

    var body: some View {
        VStack {
            Text(item.name)
            Button("Press Me") {
                item.name = "Watermelon"
                merchant.updateFruits(with: item)
            }
        }
    }
}

huangapple
  • 本文由 发表于 2023年2月18日 20:17:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/75493273.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定