私はSwitUIで遊んでおり、ObservableObjectの仕組みを理解しようとしています。 Personオブジェクトの配列があります。新しいPersonを配列に追加すると、ビューに再読み込みされます。ただし、既存のPersonの値を変更すると、ビューに再ロードされません
// NamesClass.Swift
import Foundation
import SwiftUI
import Combine
class Person: ObservableObject,Identifiable{
var id: Int
@Published var name: String
init(id: Int, name: String){
self.id = id
self.name = name
}
}
class People: ObservableObject{
@Published var people: [Person]
init(){
self.people = [
Person(id: 1, name:"Javier"),
Person(id: 2, name:"Juan"),
Person(id: 3, name:"Pedro"),
Person(id: 4, name:"Luis")]
}
}
struct ContentView: View {
@ObservedObject var mypeople: People
var body: some View {
VStack{
ForEach(mypeople.people){ person in
Text("\(person.name)")
}
Button(action: {
self.mypeople.people[0].name="Jaime"
//self.mypeople.people.append(Person(id: 5, name: "John"))
}) {
Text("Add/Change name")
}
}
}
}
この行のコメントを外して新しいPerson(John)を追加すると、Jaimeの名前が正しく表示されます。ただし、名前を変更しただけでは、ビューに表示されません。
何かがおかしいのではないか、またはObservedObjectsが配列でどのように機能するのかわかりません。
ヘルプや説明は大歓迎です!
Person
はクラスなので、参照型です。変更しても、People
配列は変更されないため、サブジェクトからは何も出力されません。ただし、手動で呼び出して通知することもできます。
Button(action: {
self.mypeople.objectWillChange.send()
self.mypeople.people[0].name="Jaime"
}) {
Text("Add/Change name")
}
あるいは(できれば)、クラスの代わりに構造体を使用することもできます。また、ObservableObjectに準拠する必要も、手動で.send()を呼び出す必要もありません。
import Foundation
import SwiftUI
import Combine
struct Person: Identifiable{
var id: Int
var name: String
init(id: Int, name: String){
self.id = id
self.name = name
}
}
class People: ObservableObject{
@Published var people: [Person]
init(){
self.people = [
Person(id: 1, name:"Javier"),
Person(id: 2, name:"Juan"),
Person(id: 3, name:"Pedro"),
Person(id: 4, name:"Luis")]
}
}
struct ContentView: View {
@ObservedObject var mypeople: People = People()
var body: some View {
VStack{
ForEach(mypeople.people){ person in
Text("\(person.name)")
}
Button(action: {
self.mypeople.people[0].name="Jaime"
}) {
Text("Add/Change name")
}
}
}
}
それが役立つと思うかもしれない人のために。これは@kontikiの回答に対するより一般的なアプローチです。
このようにして、さまざまなモデルクラスタイプで自分自身を繰り返す必要はありません
import Foundation
import Combine
import SwiftUI
class ObservableArray<T>: ObservableObject {
@Published var array:[T] = []
var cancellables = [AnyCancellable]()
init(array: [T]) {
self.array = array
}
func observeChildrenChanges<T: ObservableObject>() -> ObservableArray<T> {
let array2 = array as! [T]
array2.forEach({
let c = $0.objectWillChange.sink(receiveValue: { _ in self.objectWillChange.send() })
// Important: You have to keep the returned value allocated,
// otherwise the sink subscription gets cancelled
self.cancellables.append(c)
})
return self as! ObservableArray<T>
}
}
class Person: ObservableObject,Identifiable{
var id: Int
@Published var name: String
init(id: Int, name: String){
self.id = id
self.name = name
}
}
struct ContentView : View {
//For observing changes to the array only.
//No need for model class(in this case Person) to conform to ObservabeObject protocol
@ObservedObject var mypeople: ObservableArray<Person> = ObservableArray(array: [
Person(id: 1, name:"Javier"),
Person(id: 2, name:"Juan"),
Person(id: 3, name:"Pedro"),
Person(id: 4, name:"Luis")])
//For observing changes to the array and changes inside its children
//Note: The model class(in this case Person) must conform to ObservableObject protocol
@ObservedObject var mypeople: ObservableArray<Person> = try! ObservableArray(array: [
Person(id: 1, name:"Javier"),
Person(id: 2, name:"Juan"),
Person(id: 3, name:"Pedro"),
Person(id: 4, name:"Luis")]).observeChildrenChanges()
var body: some View {
VStack{
ForEach(mypeople.array){ person in
Text("\(person.name)")
}
Button(action: {
self.mypeople.people[0].name="Jaime"
//self.mypeople.people.append(Person(id: 5, name: "John"))
}) {
Text("Add/Change name")
}
}
}
}
この問題にはもっとエレガントな解決策があると思います。 objectWillChange
メッセージをモデル階層の上位に伝搬する代わりに、リストの行のカスタムビューを作成して、各アイテムが@ObservedObjectになるようにすることができます。
struct PersonRow: View {
@ObservedObject var person: Person
var body: some View {
Text(person.name)
}
}
struct ContentView: View {
@ObservedObject var mypeople: People
var body: some View {
VStack{
ForEach(mypeople.people){ person in
PersonRow(person: person)
}
Button(action: {
self.mypeople.people[0].name="Jaime"
//self.mypeople.people.append(Person(id: 5, name: "John"))
}) {
Text("Add/Change name")
}
}
}
}
一般に、List/ForEachのアイテムのカスタムビューを作成すると、コレクションの各アイテムの変更を監視できます。