首页 > 解决方案 > 如何更改 @Published var 会影响另一个 @Published var,例如 Binding

问题描述

有没有办法让@Published var 改变它会影响另一个@Published var,比如Binding var?

import SwiftUI
struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
    ForEach(viewModel.items, id: \.self){ item in
        Button("Select \(item.title)"){
        viewModel.selectedItem = item
        viewModel.selectedItem.title = "grape"
        print(viewModel.items) // <-- This will print [Test.Item(title: "Apple"), Test.Item(title: "Banana"), Test.Item(title: "Orange")]
      }
    }
}
}
struct Item: Hashable {
    var title: String
}
class ViewModel: ObservableObject {
    @Published var items: [Item] = [Item(title: "Apple"), Item(title: "Banana"), Item(title: "Orange")]
    @Published var selectedItem: Item = Item(title: "default")  //<-- I want this to be a binding of an item in the bucket above, so what I modify to selectedItem will affect item in the bucket.
}

标签: swiftswiftui

解决方案


三种可能的解决方案——就一个 @Published 对另一个作出反应而言,最后一个可能最接近您的要求。


这是使用的版本didSet

struct ContentView: View {
    @StateObject var viewModel = ViewModel()
    var body: some View {
        ForEach(viewModel.items, id: \.self){ item in
            Button("Select \(item.title)"){
                viewModel.selectedItem = item
                viewModel.selectedItem?.title = "grape"
                print(viewModel.items)
            }
        }
    }
}

struct Item: Hashable {
    var id = UUID()
    var title: String
}

class ViewModel: ObservableObject {
    @Published var selectedItem : Item? {
        didSet { //when there's a new value, see if it should be mapped into the original item set
            self.items = self.items.map {
                if let selectedItem = selectedItem, selectedItem.id == $0.id {
                    return selectedItem
                }
                return $0
            }
        }
    }
    @Published var items: [Item] = [Item(title: "Apple"), Item(title: "Banana"), Item(title: "Orange")]
}

这是一种可能性,使用自定义Binding

struct ContentView: View {
    @StateObject var viewModel = ViewModel()
    var body: some View {
        ForEach(viewModel.items, id: \.self){ item in
            Button("Select \(item.title)"){
                viewModel.selectedItemID = item.id
                viewModel.selectedItem?.wrappedValue.title = "grape"
                print(viewModel.items)
            }
        }
    }
}

struct Item: Hashable {
    var id = UUID()
    var title: String
}

class ViewModel: ObservableObject {
    @Published var selectedItemID : UUID?
    @Published var items: [Item] = [Item(title: "Apple"), Item(title: "Banana"), Item(title: "Orange")]
    
    var selectedItem : Binding<Item>? {
        guard let selectedItemID = selectedItemID else {
            return nil
        }
        return .init { () -> Item in
            self.items.first(where: {$0.id == selectedItemID}) ?? Item(title: "")
        } set: { (item) in
            self.items = self.items.map {
                if $0.id == selectedItemID { return item }
                return $0
            }
        }
    }
}

最后,使用 Combine 的版本:


import SwiftUI
import Combine

struct ContentView: View {
    @StateObject var viewModel = ViewModel()
    var body: some View {
        ForEach(viewModel.items, id: \.self){ item in
            Button("Select \(item.title)"){
                viewModel.selectedItem = item
                viewModel.selectedItem?.title = "grape"
                print(viewModel.items)
            }
        }
    }
}

struct Item: Hashable {
    var id = UUID()
    var title: String
}

class ViewModel: ObservableObject {
    @Published var selectedItem : Item?
    @Published var items: [Item] = [Item(title: "Apple"), Item(title: "Banana"), Item(title: "Orange")]
    
    private var cancellable : AnyCancellable?
    
    init() {
        cancellable = $selectedItem
            .compactMap { $0 } //remove nil values
            .sink(receiveValue: { (newValue) in
            self.items = self.items.map {
                newValue.id == $0.id ? newValue : $0 //if the ids match, return the new value -- if not, return the old one
            }
        })
    }
}

这三者都有相同的功能——这真的取决于你喜欢哪种编码风格。我个人可能会选择最后一个选项,因为 Combine 和 SwiftUI 往往可以很好地协同工作。它也很容易通过过滤等进行扩展。


推荐阅读