Опубликованные работы для одного объекта, но не для массива объектов
Я пытаюсь сделать индивидуально подвижные объекты. Я могу успешно сделать это для одного объекта, но как только я помещаю его в массив, объекты больше не могут двигаться.
Модель:
class SocialStore: ObservableObject {
@Published var socials : [Social]
init(socials: [Social]){
self.socials = socials
}
}
class Social : ObservableObject{
var id: Int
var imageName: String
var companyName: String
@Published var pos: CGPoint
init(id: Int, imageName: String, companyName: String, pos: CGPoint) {
self.id = id
self.imageName = imageName
self.companyName = companyName
self.pos = pos
}
var dragGesture : some Gesture {
DragGesture()
.onChanged { value in
self.pos = value.location
print(self.pos)
}
}
}
Несколько изображений (изображения, не следующие за перетаскиванием):
struct ContentView : View {
@ObservedObject var socialObject: SocialStore = SocialStore(socials: testData)
@ObservedObject var images: Social = testData[2]
var body: some View {
VStack {
ForEach(socialObject.socials, id: \.id) { social in
Image(social.imageName)
.position(social.pos)
.gesture(social.dragGesture)
}
}
}
}
Одиночное изображение (изображение после жеста):
struct ContentView : View {
@ObservedObject var socialObject: SocialStore = SocialStore(socials: testData)
@ObservedObject var images: Social = testData[2]
var body: some View {
VStack {
Image(images.imageName)
.position(images.pos)
.gesture(images.dragGesture)
}
}
}
Я ожидаю, что отдельные предметы смогут свободно перемещаться. Я вижу, что координаты обновляются, но положение каждого изображения нет.
3 ответа
Во-первых, отказ от ответственности: приведенный ниже код не предназначен для копирования и вставки. Его единственная цель - помочь вам понять проблему. Могут быть более эффективные способы ее решения, поэтому не торопитесь думать о своей реализации, как только поймете проблему.
Почему вид не обновляется?: @Publisher
в SocialStore
будет обновлять только при изменении массива. Поскольку из массива ничего не добавляется и не удаляется, ничего не произойдет. Кроме того, поскольку элементы массива являются объектами (а не значениями), когда они действительно изменяют свою позицию, массив остается неизменным, поскольку ссылка на объекты остается неизменной. Помните: классы создают объекты, структуры создают значения.
Нам нужен способ создания магазина, чтобы генерировать изменения, когда что-то в его элементе меняется. В приведенном ниже примере ваш магазин будет подписываться на каждую из привязок своих элементов. Теперь все опубликованные обновления ваших товаров будут переданы издателю вашего магазина, и вы получите желаемый результат.
import SwiftUI
import Combine
class SocialStore: ObservableObject {
@Published var socials : [Social]
var cancellables = [AnyCancellable]()
init(socials: [Social]){
self.socials = socials
self.socials.forEach({
let c = $0.objectWillChange.sink(receiveValue: { self.objectWillChange.send() })
// Important: You have to keep the returned value allocated,
// otherwise the sink subscription gets cancelled
self.cancellables.append(c)
})
}
}
class Social : ObservableObject{
var id: Int
var imageName: String
var companyName: String
@Published var pos: CGPoint
init(id: Int, imageName: String, companyName: String, pos: CGPoint) {
self.id = id
self.imageName = imageName
self.companyName = companyName
self.pos = pos
}
var dragGesture : some Gesture {
DragGesture()
.onChanged { value in
self.pos = value.location
print(self.pos)
}
}
}
struct ContentView : View {
@ObservedObject var socialObject: SocialStore = SocialStore(socials: testData)
var body: some View {
VStack {
ForEach(socialObject.socials, id: \.id) { social in
Image(social.imageName)
.position(social.pos)
.gesture(social.dragGesture)
}
}
}
}
Для тех, кому это может пригодиться. Это более общий подход к ответу @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<K>(_ type:K.Type) throws ->ObservableArray<T> where K : ObservableObject{
let array2 = array as! [K]
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
}
}
class Social : ObservableObject{
var id: Int
var imageName: String
var companyName: String
@Published var pos: CGPoint
init(id: Int, imageName: String, companyName: String, pos: CGPoint) {
self.id = id
self.imageName = imageName
self.companyName = companyName
self.pos = pos
}
var dragGesture : some Gesture {
DragGesture()
.onChanged { value in
self.pos = value.location
print(self.pos)
}
}
}
struct ContentView : View {
//For observing changes to the array only.
//No need for model class(in this case Social) to conform to ObservabeObject protocol
@ObservedObject var socialObject: ObservableArray<Social> = ObservableArray(array: testData)
//For observing changes to the array and changes inside its children
//Note: The model class(in this case Social) must conform to ObservableObject protocol
@ObservedObject var socialObject: ObservableArray<Social> = try! ObservableArray(array: testData).observeChildrenChanges(Social.self)
var body: some View {
VStack {
ForEach(socialObject.array, id: \.id) { social in
Image(social.imageName)
.position(social.pos)
.gesture(social.dragGesture)
}
}
}
}
Вам необходимо реализовать
let objectWillChange = ObservableObjectPublisher()
в вашем классе ObservableObject
Есть два ObservableObject
типы и тот, который вас интересует, это Combine.ObservableObject
, Это требует objectWillChange
переменная типа ObservableObjectPublisher
и именно это SwiftUI использует для запуска нового рендеринга. Я не уверен что Foundation.ObservableObject
используется для, но это сбивает с толку.
@Published
создает PassthroughSubject
издатель, который может быть подключен к приемнику в другом месте, но бесполезен для SwiftUI, кроме .onReceive()
конечно.