如何解决SwiftUI 组合:嵌套观察对象 型号视图模型建议的解决方案
让 swiftUI 仍然基于嵌套的观察对象进行更新的最佳方法是什么?
以下示例显示了我对嵌套观察对象的含义。 ball manager 的 balls 数组是一个已发布的属性,它包含一组可观察对象,每个对象都有一个已发布的属性本身(颜色字符串)。
不幸的是,当点击其中一个球时,它不会更新球名称,也不会收到更新。所以我可能搞砸了在那种情况下联合收割机是如何工作的?
import SwiftUI
class Ball: Identifiable,ObservableObject {
let id: UUID
@Published var color: String
init(ofColor color: String) {
self.id = UUID()
self.color = color
}
}
class BallManager: ObservableObject {
@Published var balls: [Ball]
init() {
self.balls = []
}
}
struct Arena: View {
@StateObject var bm = BallManager()
var body: some View {
vstack(spacing: 20) {
ForEach(bm.balls) { ball in
Text(ball.color)
.onTapGesture {
changeBall(ball)
}
}
}
.onAppear(perform: createBalls)
.onReceive(bm.$balls,perform: {
print("ball update: \($0)")
})
}
func createBalls() {
for i in 1..<4 {
bm.balls.append(Ball(ofColor: "c\(i)"))
}
}
func changeBall(_ ball: Ball) {
ball.color = "cx"
}
}
解决方法
您只需创建一个 BallView
并观察它并从那里进行更改。你必须直接观察每个ObservableObject
struct Arena: View {
@StateObject var bm = BallManager()
var body: some View {
VStack(spacing: 20) {
ForEach(bm.balls) { ball in
BallView(ball: ball)
}
}
.onAppear(perform: createBalls)
.onReceive(bm.$balls,perform: {
print("ball update: \($0)")
})
}
func createBalls() {
for i in 1..<4 {
bm.balls.append(Ball(ofColor: "c\(i)"))
}
}
}
struct BallView: View {
@ObservedObject var ball: Ball
var body: some View {
Text(ball.color)
.onTapGesture {
changeBall(ball)
}
}
func changeBall(_ ball: Ball) {
ball.color = "cx"
}
}
,
当 Ball
数组中的 balls
发生变化时,您可以调用 objectWillChange.send()
来更新 ObservableObject
。
以下内容应该适合您:
class BallManager: ObservableObject {
@Published var balls: [Ball] {
didSet { setCancellables() }
}
let ballPublisher = PassthroughSubject<Ball,Never>()
private var cancellables = [AnyCancellable]()
init() {
self.balls = []
}
private func setCancellables() {
cancellables = balls.map { ball in
ball.objectWillChange.sink { [weak self] in
guard let self = self else { return }
self.objectWillChange.send()
self.ballPublisher.send(ball)
}
}
}
}
并通过以下方式获取更改:
.onReceive(bm.ballPublisher) { ball in
print("ball update:",ball.id,ball.color)
}
注意:如果传入的是 balls
的初始值并且不总是一个空数组,则您还应该在 init 中调用 setCancellables()
。
型号
Ball
是模型,可以是结构体或类(通常建议使用结构体,您可以找到更多信息 here)
视图模型
通常您将 ObservableObject
用作管理数据的 ViewModel
或组件。通常为球(模型)设置默认值是很常见的,因此您可以设置一个空数组。然后,您可以使用来自数据库或存储的网络请求来填充模型。
BallManager
可以重命名为 BallViewModel
BallViewModel
具有根据 ForEach
组件中的索引更改底层模型的功能。 id: \.self
基本上呈现当前索引的球(模型)。
建议的解决方案
以下更改将有助于实现您想要做的事情
struct Ball: Identifiable {
let id: UUID
var color: String
init(ofColor color: String) {
self.id = UUID()
self.color = color
}
}
class BallViewModel: ObservableObject {
@Published var balls: [Ball] = []
func changeBallColor(in index: Int,color: String) {
balls[index] = Ball(ofColor: color)
}
}
struct Arena: View {
@StateObject var bm = BallViewModel()
var body: some View {
VStack(spacing: 20) {
ForEach(bm.balls.indices,id: \.self) { index in
Button(bm.balls[index].color) {
bm.changeBallColor(in: index,color: "cx")
}
}
}
.onAppear(perform: createBalls)
.onReceive(bm.$balls,perform: {
print("ball update: \($0)")
})
}
func createBalls() {
for i in 1..<4 {
bm.balls.append(Ball(ofColor: "c\(i)"))
}
}
}
,
对于此示例,您不需要嵌套的 ObserverObjects
:
模型应该是一个简单的结构:
struct Ball: Identifiable {
let id: UUID
let color: String
init(id: UUID = UUID(),color: String) {
self.id = id
self.color = color
}
}
ViewModel 应该处理所有的逻辑,这就是为什么我将所有操作球的函数移到这里并将球数组设为私有的原因。因为调用 changeBall 会将数组中的一个结构替换为另一个 objectWillChange
被触发,因此视图会更新并触发 onReceive
。
class BallManager: ObservableObject {
@Published private (set) var balls = [Ball]()
func changeBall(_ ball: Ball) {
guard let index = balls.firstIndex(where: { $0.id == ball.id }) else { return }
balls[index] = Ball(id: ball.id,color: "cx")
}
func createBalls() {
for i in 1..<4 {
balls.append(Ball(color: "c\(i)"))
}
}
}
View 应该只是将用户意图传达给 ViewModel:
struct Arena: View {
@StateObject var ballManager = BallManager()
var body: some View {
VStack(spacing: 20) {
ForEach(ballManager.balls) { ball in
Text(ball.color)
.onTapGesture {
ballManager.changeBall(ball)
}
}
}
.onAppear(perform: ballManager.createBalls)
.onReceive(ballManager.$balls) {
print("ball update: \($0)")
}
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。