0

也许你可以照亮我黑暗无知的道路?我的目标是一个 SwiftUI 应用程序,我可以在其中将组件注入视图并让系统在这些组件上运行一些逻辑。每次我觉得我已经到了;基础的弱点让我重回起点。让我告诉你我试过什么!

protocol DataComponent: Component {
    associatedtype DataType
    var data: DataType { get set }
    init()
    init(data: DataType)
}

struct NameComponent: DataComponent { var data = "default" }

这是基本的数据组件。我将EntitasKit用于我的实体组件系统。

enum Entities {
    case source
    ...
}

protocol EntityServicing {
    var context: Context { get }
    func entity(_ entity: Entities) -> Entity
}

final class EntityService: EntityServicing {
    let context = Context()
    func entity(_ entity: Entities) -> Entity {
        switch entity {
        case .source:
            guard let first = context.entities.first else {
                return context.createEntity()
            }
            return first
        ...
        }
    }
}

这是应用程序范围的 EntityService,它包含实体及其组件的上下文。它是使用Resolver注入的。

现在,这是 SwiftUI 中的视图。

struct ContentView: View {
    @EntityState(.source, "Dale Cooper") var name: NameComponent
    var body: some View {
        Button(action: { name = NameComponent(data: "Dougie Jones") }) {
            Text(name.data).padding()
        }
    }
}

EntityState 就是它听起来的样子;一个属性包装器,它捕获实体组件的状态并使其对 SwiftUI 可见。

@propertyWrapper
struct EntityState<C>: DynamicProperty where C: DataComponent {

    @State private var state = C()
    @Injected private var entityService: EntityServicing
    private let entityID: Entities
    private var entity: Entity { entityService.entity(entityID) }
    
    init(_ id: Entities, _ data: C.DataType? = nil) {
        self.entityID = id
        if let data = data {
            entity.set(C(data: data))
        } else {
            guard entity.has(C.cid) else {
                entity.set(state)
                return
            }
        }
        _state = State(wrappedValue: entity.get()!)
    }

    public var wrappedValue: C {
        get { entity.get()! }
        nonmutating set { state = newValue }
    }
        
    mutating func update() {
        entity.set(state)
    }
}

SwiftUI 会注意到动态属性中的状态变化。单击该按钮将重新呈现视图并更新实体上的组件。

当我添加一个更改组件的系统时,这个基础开始崩溃。

class NameSystem: ReactiveSystem {
    
    @Injected var entityService: EntityServicing
    lazy var collector: Collector = Collector(
        group: entityService.context.group(
            Matcher(
                all: [NameComponent.cid]
            )
        ),
        type: .added
    )
    let name = "Name System Added Here!"

    func execute(entities: Set<Entity>) {
        for e in entities {
            if let data = e.get(NameComponent.self)?.data {
                e.set(NameComponent(data: "Special Agent " + data))
            }
        }
    }
}

问题是 SwiftUI 状态永远不会被通知,但我希望 SwiftUI 知道组件在系统更新时发生的变化。

我尝试了许多不同的方法:

  • 使用@Published 将组件/实体/上下文作为 ObservableObject。我的理解是,这是将数据模型连接到 SwiftUI 的好方法。我有@Published 组件、组件和实体中的数据,但一直阻止我的问题是让 EntityState 重新渲染视图。由于上下文被注入,我不能使用其他属性包装器,如@ObservedObject。
  • 绑定。我尝试观察 EntityState 中的组件对象并将组件绑定传递给实体,但我无法将绑定存储到同一个字典中的不同 DataComponents。我什至不确定对绑定的更改是否会返回到 EntityState 以重新呈现视图。同样的限制也适用于@States 的绑定。无论如何,我已经把自己困在这条路上了。
  • 发布者和订阅者。将 EntityState 转换为订阅者以便它可以接收值需要它是可变的。要使 EntityState 可变,它必须成为一个类,但这会破坏 SwiftUI 重新渲染。发布者可以是某种主题或组件的自定义发布者。我只是无法解决如何接收值并将它们分配给状态,因此 SwiftUI 重新呈现。整个联合区域对我来说有点模糊。可能有一种聪明的方法可以做一些我不知道的惊人的事情。

如何从系统将组件分配给状态,以便 SwiftUI 重新渲染?

我希望你发现这是一个有趣的问题来解决,它让你和我一样对解决方案感到好奇。至少让我很兴奋。

祝大家拥有美好的一天!

4

0 回答 0