Опубликованные работы для одного объекта, но не для массива объектов

Я пытаюсь сделать индивидуально подвижные объекты. Я могу успешно сделать это для одного объекта, но как только я помещаю его в массив, объекты больше не могут двигаться.

Модель:

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() конечно.

Другие вопросы по тегам