12

我正在尝试制作可单独移动的物体。我能够成功地为一个对象做到这一点,但是一旦我将它放入一个数组中,这些对象就不能再移动了。

模型:

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)
        }
    }
}

我希望单个项目能够自由移动。我看到坐标正在更新,但每个图像的位置都没有。

4

4 回答 4

17

首先,免责声明:下面的代码并不意味着复制和粘贴解决方案。它的唯一目标是帮助您了解挑战。可能有更有效的方法来解决它,所以一旦你理解了问题,就花点时间考虑你的实现。


为什么视图不更新?@PublisherinSocialStore只会在数组更改时发出更新。由于没有从数组中添加或删除任何内容,因此不会发生任何事情。此外,因为数组元素是对象(而不是值),所以当它们改变位置时,数组保持不变,因为对对象的引用保持不变。记住:类创建对象,结构创建

我们需要一种创建 store 的方法,当其元素中的某些内容发生变化时发出变化。在下面的示例中,您的商店将订阅其每个元素绑定。现在,您商品的所有已发布更新都将转发给您的商店发布商,您将获得所需的结果。

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)
            }
        }
    }
}
于 2019-08-01T06:04:56.413 回答
3

对于那些可能会觉得有帮助的人。这是@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)
            }
        }
    }
}

于 2019-09-09T14:56:37.447 回答
0

有两种ObservableObject类型,您感兴趣的一种是Combine.ObservableObject. 它需要一个objectWillChange类型的变量ObservableObjectPublisher,SwiftUI 使用它来触发新的渲染。我不确定Foundation.ObservableObject它的用途,但它令人困惑。

@Published创建一个PassthroughSubject可以连接到其他地方的接收器但对 SwiftUI 没有用处的发布者,.onReceive()当然除了。

于 2019-08-01T06:01:28.883 回答
0

你需要实施

let objectWillChange = ObservableObjectPublisher()

在您的 ObservableObject 类中

于 2019-08-01T07:16:08.447 回答