对象的属性未在数组 SwiftUI 中更新

Asi*_*taq 0 ios swift swiftui

我从 PHPicker 中挑选照片并在SwiftUI 中的VStackusingForEach循环中显示它们。我将图像包装在具有 3 个属性的对象中。

class UploadedImage: Hashable, Equatable {
    var id: Int
    var image : UIImage
    @State var quantity: Int
    
    init(id: Int, image: UIImage, quantity: Int) {
        self.id = id
        self.image = image
        self.quantity = quantity
    }
    
    static func == (lhs: UploadedImage, rhs: UploadedImage) -> Bool {
        return lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(self.id)
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,在选择器选择图像后,我将该对象添加到数组中,然后使用 ForEach 在屏幕上显示。

if !uploadedImages.isEmpty {
    ScrollView {
        VStack{
            ForEach(uploadedImages, id: \.self) { selectedImage in
                    HStack{
                        ZStack{
  // some other code to display image...
                        }
                        Spacer()
                        HStack{
                            Button(action: {
                                if let index = self.uploadedImages.firstIndex(where: {$0.id == selectedImage.id}){
                                    uploadedImages[index] = UploadedImage(id: selectedImage.id, url: selectedImage.url, quantity: (1))
                                }
                            }, label: {
                                Text("-")
                            })
                            Text("\(selectedImage.quantity)")
                            Button(action: {
                                if let index = self.uploadedImages.firstIndex(where: {$0.id == selectedImage.id}){
                                    uploadedImages[index] = UploadedImage(id: selectedImage.id, url: selectedImage.url, quantity: selectedImage.quantity + 1)
                                }
                            }, label: {
                                Text("+")
                            })
                        }
                    }
                    .padding()
                    .border(Color.black, width: 2)
                    .cornerRadius(6)
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在 ForEach 循环中,我显示了两个按钮,+, -用于增加和减少数量。

该按钮上的操作是以下代码。

if let index = self.uploadedImages.firstIndex(where: {$0.id == selectedImage.id}){
   uploadedImages[index] = UploadedImage(id: selectedImage.id, url: selectedImage.url, quantity: selectedImage.quantity + 1)
}
Run Code Online (Sandbox Code Playgroud)

但数量没有更新。我不知道为什么。

id当我在新更新的对象中更改时。

uploadedImages[index] = UploadedImage(id: 89282, url: selectedImage.url, quantity: selectedImage.quantity + 1)
Run Code Online (Sandbox Code Playgroud)

那么数量只更新一次。

有什么好的办法增加和减少数量吗?

Ale*_*ges 5

SwiftUI 中的数据流最初可能有点难以适应,具体取决于您\xe2\x80\x99 来自哪个框架。

\n

有多种方法可以从高层角度来处理它,但考虑到您的问题是关于单个视图、其模型和按钮的本地化问题,我将重点关注来自单个视图\xe2\x80\x99s 的数据看法。

\n

然后,为了了解数据如何在视图之间流动、它如何与 SwiftUI\xe2\x80\x99s 视图生命周期交互等等,我会推荐以下两个 WWDC 会议:

\n\n

\xe2\x80\x99s 还有 Apple 提供的这份文档,其中包含相当详细的分类:\n模型数据。\nSwiftUI 教程中的这一部分:管理状态和生命周期

\n

返回到 View\xe2\x80\x99s 视角。\n根据数据的两个特征,在视图中设置数据的方式将有很大不同:

\n
    \n
  1. 数据是值类型还是引用类型
  2. \n
  3. 您希望 SwiftUI 处理数据的生命周期还是您想自己管理?
  4. \n
\n

因此,在该矩阵的简单端,您有一个让 SwiftUI 为您处理的值类型。\n这就是@State的用途。

\n

在视图内部,您可以像这样使用 @State 来处理字符串:

\n
@State var name = \xe2\x80\x9cBob\xe2\x80\x9d\n
Run Code Online (Sandbox Code Playgroud)\n

但结构体也可以工作:

\n
@State var selectedUser = MyUserStruct()\n
Run Code Online (Sandbox Code Playgroud)\n

关于 @State 需要了解的重要一点是,它明确意味着可能的场景的简单结束。

\n

@状态

\n
    \n
  • 旨在仅在视图中使用
  • \n
  • 需要数据是值类型才能正常运行
  • \n
  • 与View的生命周期绑定
  • \n
\n

它\xe2\x80\x99s 用于表示视图内部状态的数据。

\n

请注意,在这种情况下,无需对我们的数据执行任何操作,即可让 SwiftUI 了解何时更新视图。(至少对于大多数用例而言。)

\n

复杂性上升:\n如果您希望 SwiftUI 处理数据,但正在处理引用类型,该怎么办?\n这是您当前的设置。

\n

与值类型示例相比,SwiftUI 需要额外的帮助来了解何时更新依赖于引用类型的视图。

\n

因此,任何应驱动视图更新的类都需要符合ObservableObject协议。\n此外,在这些类中,只有标记为@Published 的属性在这些属性更改时才会真正驱动视图更新。

\n

然后,要在视图中使用 @ObservableObject,您可以选择几个具有截然不同行为的属性包装器:\n @StateObject@ObservedObject@EnvironmentObject

\n

@StateObject 的行为与@State 最接近。该对象由 SwiftUI 管理,并且初始化为 @StateObject 的 ObservableObject 的生命周期绑定到初始化它的 View 的生命周期。\n@ObservedObject 和 @EnvironmentObject 与我们当前的不匹配复杂性矩阵中的位置,因为它们适用于您不希望 SwiftUI 处理您的数据生命周期(或不直接处理)的情况。

\n

但这实际上为我们提供了足够的信息来重组您的示例并保持在我们的单一视图视角内。

\n

让\xe2\x80\x99s 首先从字面上看你的例子,并假装我们只想在数组中进行这些本地化更新,而\xe2\x80\x99s 就是它。\n对于这种情况,@State 实际上可以工作,但我们需要将 UploadedImage 模型更改为结构:

\n
struct UploadedImage: Identifiable {\n    let id: Int\n    let image: Image\n    var quantity: Int\n}\n
Run Code Online (Sandbox Code Playgroud)\n

两个注意事项:

\n
    \n
  • 我将您的图像更改为使用 SwiftUI\xe2\x80\x99s 图像类型而不是 UIKit\xe2\x80\x99s UIImage。如果您绝对需要处理 UIImage 实例,后者仍然适用于本示例的目的。
  • \n
  • Identificate 是一个协议,它告诉 SwiftUI 使用 id 属性(无论如何你已经有了)来识别 ForEach 等地方的数据。因此,我们\xe2\x80\x99不必遵守Hashable,也不需要明确告诉ForEach如何识别每个UploadedImage。
  • \n
\n

现在我们\xe2\x80\x99将在视图中通过@State使用它:

\n
@State var uploadedImages = [\n    UploadedImage(id: 1, image: someImage, quantity: 4),\n    UploadedImage(id: 2, image: someImage, quantity: 8),\n    UploadedImage(id: 3, image: someImage, quantity: 16)\n]\n
Run Code Online (Sandbox Code Playgroud)\n

如上所述,ForEach 可以简化为:

\n
ForEach(uploadedImages) { selectedImage in\n    // ...\n}\n
Run Code Online (Sandbox Code Playgroud)\n

而 \xe2\x80\x99 实际上是使您的示例正常工作所需的全部内容。

\n

但正如其他人指出的那样,\xe2\x80\x99s 可能你可能有其他逻辑需要处理,并且将一些非 UI 逻辑拆分到单独的视图模型中可能会很好,让视图进行交互使用该模型,并观察它的变化。

\n

通常,视图模型采用类的形式,因此我们改变上面讨论的引用类型路线的方法:我们的视图模型需要符合 ObservableObject,使用 @Published 来显示任何应该驱动更新的数据,并且它得到通过@StateObject交给SwiftUI来管理。

\n

综合起来,该解决方案如下所示:

\n
@MainActor class UploadedImagesViewModel: ObservableObject {\n    @Published var images = [\n        UploadedImage(id: 3, image: someImage, quantity: 4),\n        UploadedImage(id: 4, image: someImage, quantity: 8),\n        UploadedImage(id: 5, image: someImage, quantity: 16)\n    ]\n\n    func increment(_ selectedImage: UploadedImage) {\n        if let index = self.images.firstIndex(where: {$0.id == selectedImage.id}){\n            images[index].quantity += 1\n        }\n    }\n\n    func decrement(_ selectedImage: UploadedImage) {\n        if let index = self.images.firstIndex(where: {$0.id == selectedImage.id}){\n            images[index].quantity -= 1\n        }\n    }\n}\n\nstruct UploadedImage: Identifiable {\n    let id: Int\n    let image: Image\n    var quantity: Int\n}\n\nstruct UploadedImagesView: View {\n\n    @StateObject var viewModel = UploadedImagesViewModel()\n\n    var body: some View {\n    \n        if !viewModel.images.isEmpty {\n            ScrollView {\n                VStack{\n                    ForEach(viewModel.images) { selectedImage in\n                        HStack{\n                            ZStack{\n                                // some other code to display image...\n                            }\n                            Spacer()\n                            HStack{\n                                Button(action: {\n                                    viewModel.decrement(selectedImage)\n                                }, label: {\n                                    Text("-")\n                                })\n                                Text("\\(selectedImage.quantity)")\n                                Button(action: {\n                                    viewModel.increment(selectedImage)\n                                }, label: {\n                                    Text("+")\n                                })\n                            }\n                        }\n                        .padding()\n                        .border(Color.black, width: 2)\n                        .cornerRadius(6)\n                    }\n                }\n            }\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

最后一些最后的注意事项:

\n
    \n
  • 正如您所看到的,实际数据仍保持结构形式。在某些情况下,您可能实际上需要一组其他引用类型,例如其他视图模型的数组,但这些情况很少见并且很难正常工作。ViewModel 的类和实际数据模型的结构的这种组合是最常见的方法。
  • \n
  • @MainActor与 Swift\xe2\x80\x99s 新并发模型相关,详细信息超出了本答案的范围。但它\xe2\x80\x99s通常适用于视图模型/ObservableObjects是一个好主意,因为它将确保(并通过编译器错误引导您)仅从主线程更新ViewModel。
  • \n
\n