【问题标题】:SwiftUI store and update Custom object in UserDefault arraySwiftUI 在 UserDefault 数组中存储和更新自定义对象
【发布时间】:2021-10-26 00:52:50
【问题描述】:

我正在制作一个非常复杂的 SwiftUI 应用程序,它可以让您注册步行。
UI 本身运行良好,但我终其一生都无法弄清楚如何使用 UserDefaults 在本地正确存储数据。

我正在尝试存储带有子 Profile 对象的用户对象。
我正在分别尝试存储一个 DogArray 对象,该对象具有 Dog 对象列表。
像这样:

 - User Defaults
   - User
     - Profile
   - DogArray
     - Dog
       - Walk
       - Walk
     - Dog
       - Walk
     ...

我可以存储 User 和 Profile 对象,但我首先必须从 ProfileView 导航回来,然后才能关闭应用程序才能存储它们。我也可以将 Dog 对象存储在 DogArray 中,但是当我在 DogDetailView 中更改 Dog 时它似乎不会更新 UserDefaults,并且在重新启动应用程序时不会存储它们的数据,只有 DogArray。

我意识到克隆项目比尝试将我的代码 sn-ps 拼凑起来要容易得多。我已将一个最小可行示例上传到 github,您可以在其中自己尝试。谢谢

https://github.com/kimnordin/WalkApp

如果您运行它并更改某些内容,您会发现它没有正确存储,或者根本没有。

这是我的 DogArrayUserWalk objects(walks 可以存储在 Dog 中) .

DogArray

class DogArray: ObservableObject, Codable {
    enum CodingKeys: CodingKey {
        case list
    }
    @Published var list: [Dog] {
        didSet {
            let encoder = JSONEncoder()
            if let encoded = try? encoder.encode(list) {
                UserDefaults.standard.set(encoded, forKey: "Dogs")
            }
        }
    }
    init() {
        if let dog = UserDefaults.standard.data(forKey: "Dogs") {
            let decoder = JSONDecoder()
            if let decoded = try? decoder.decode([Dog].self, from: dog) {
                self.list = decoded
                return
            }
        }
        self.list = []
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        list = try container.decode([Dog].self, forKey: .list)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(list, forKey: .list)
    }
    
    var count : Int {
        return list.count
    }
    
    func addDog(dog: Dog) {
        list.append(dog)
    }
    
    func clearDogs() {
        list.removeAll()
    }
    
    func deleteDog(index: Int){
        list.remove(at: index)
    }

    func entry(index: Int) -> Dog? {
        if index >= 0 && index <= list.count {
            return list[index]
        }
        return nil
    }
}

class Dog: ObservableObject, Identifiable, Codable {
    enum CodingKeys: CodingKey {
        case name, image, startDisplayDate, walkArray, firstSelect, secondSelect
    }
    
    let id = UUID()
    var name: String
    var image: UIImage
    var startDisplayDate: Date? = Date()
    @Published var walkArray = [Walk]()
    @Published var firstSelect: Bool = false
    @Published var secondSelect: Bool = false
    
    init(name: String, image: UIImage, startDisplayDate: Date? = Date(), walkArray: [Walk] = [Walk](), firstSelect: Bool = false, secondSelect: Bool = false) {
        self.name = name
        self.image = image
        self.startDisplayDate = startDisplayDate
        self.walkArray = walkArray
        self.firstSelect = firstSelect
        self.secondSelect = secondSelect
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        name = try container.decode(String.self, forKey: .name)
        let dataImage = try container.decode(Data.self, forKey: .image)
        if let uiImage = dataImage.toImage() {
            image = uiImage
        }
        else {
            image = UIImage(systemName: "questionmark.circle.fill")!
        }
        startDisplayDate = try container.decode(Date.self, forKey: .startDisplayDate)
        walkArray = try container.decode([Walk].self, forKey: .walkArray)
        firstSelect = try container.decode(Bool.self, forKey: .firstSelect)
        secondSelect = try container.decode(Bool.self, forKey: .secondSelect)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        try container.encode(name, forKey: .name)
        try container.encode(image.toData(), forKey: .image)
        try container.encode(startDisplayDate, forKey: .startDisplayDate)
        try container.encode(walkArray, forKey: .walkArray)
        try container.encode(firstSelect, forKey: .firstSelect)
        try container.encode(secondSelect, forKey: .secondSelect)
    }
    
    func walk(time: Date, firstSelect: Bool, secondSelect: Bool) {
        let walk = Walk(time: time, firstAction: firstSelect, secondAction: secondSelect)
        walkArray.append(walk)
        sortWalks()
    }
    
    func sortWalks() {
        walkArray = walkArray.sortWalksByDates()
    }
}

用户

class User: ObservableObject {
    @Published var profile: Profile {
        didSet {
            let encoder = JSONEncoder()
            if let encoded = try? encoder.encode(profile) {
                UserDefaults.standard.set(encoded, forKey: "Profile")
            }
        }
    }
    init() {
        if let profile = UserDefaults.standard.data(forKey: "Profile") {
            let decoder = JSONDecoder()
            
            if let decoded = try? decoder.decode(Profile.self, from: profile) {
                self.profile = decoded
                return
            }
        }
        self.profile = Profile()
    }
}

struct Profile: Identifiable, Codable { // <-- Profile As Struct
    enum CodingKeys: CodingKey {
        case walkColor, firstColor, secondColor
    }
    
    let id = UUID()
    var walkColor: Color? = Color.orange
    var firstColor: Color? = Color.blue
    var secondColor: Color? = Color.pink
    
    init() {}
    
    init(walkColor: Color? = Color.orange, firstColor: Color? = Color.blue, secondColor: Color? = Color.pink) {
        self.walkColor = walkColor
        self.firstColor = firstColor
        self.secondColor = secondColor
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        let walkData = try container.decode(Data.self, forKey: .walkColor)
        let firstData = try container.decode(Data.self, forKey: .firstColor)
        let secondData = try container.decode(Data.self, forKey: .secondColor)
        walkColor = dataToColor(walkData)
        firstColor = dataToColor(firstData)
        secondColor = dataToColor(secondData)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        let walkUiColor = walkColor?.toUiColor() ?? .orange
        let firstUiColor = firstColor?.toUiColor() ?? .blue
        let secondUiColor = secondColor?.toUiColor() ?? .systemPink
        let walkData = toData(walkUiColor)
        let firstData = toData(firstUiColor)
        let secondData = toData(secondUiColor)
        
        try container.encodeIfPresent(walkData, forKey: .walkColor)
        try container.encodeIfPresent(firstData, forKey: .firstColor)
        try container.encodeIfPresent(secondData, forKey: .secondColor)
    }
    
    func dataToColor(_ data: Data) -> Color? {
        do {
            if let dataColor = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? UIColor {
                return Color(dataColor)
            }
        } catch {
            print(error)
        }
        return nil
    }
    
    func toData(_ uiColor: UIColor) -> Data? {
        do {
            let colorData = try NSKeyedArchiver.archivedData(withRootObject: uiColor, requiringSecureCoding: false)
            return colorData
        } catch {
            print(error)
        }
        return nil
    }
}

enum SelectedColor {
    case walk
    case first
    case second
    case none
}

步行

struct Walk: Codable {
    var time: Date
    var firstAction: Bool?
    var secondAction: Bool?
}

这里是我在应用启动时介绍 User 和 DogArray 对象的地方

import SwiftUI

@main
struct WalkAppApp: App {
    @StateObject private var dogData = DogArray()
    @StateObject private var user = User()
    var body: some Scene {
        WindowGroup {
            DogListView(viewModel: DogListViewModel())
                .environmentObject(user)
                .environmentObject(dogData)
        }
    }
}

这是应用中显示的第一个视图

struct DogListView: View {
    @EnvironmentObject var user: User
    @EnvironmentObject var dogs: DogArray
    @State private var presentNew = false
    @State private var presentProfile = false
    var body: some View { //MARK: View
        NavigationView {
            List {
                ForEach(0..<dogs.list.count, id: \.self) { dog in
                    ZStack(alignment: .leading) {
                        NavigationLink(destination: DogDetailView(dog: dogs.list[dog])) {
                        }
                        .opacity(0)
                        DogListRow(dog: dogs.list[dog])
                    }
                }
                .onMove(perform: move)
                .onDelete(perform: delete)
            }
            .background(NavigationLink(
                destination: NewDogView(),
                isActive: $presentNew) {
            })
            .background(NavigationLink(
                destination: ProfileView(),
                isActive: $presentProfile) {
            })
            .navigationBarTitle(Text("Dogs"))
            .navigationBarItems(leading:
                                    HStack {
                                        Button("Profile") {
                                            presentProfile = true
                                        }
                                    }
                                , trailing:
                                    HStack {
                                        Button("+") {
                                            presentNew = true
                                        }
                                    }
            )
        }
    }
    private func move(at indexSet: IndexSet, to destination: Int) {
        dogs.list.move(fromOffsets: indexSet, toOffset: destination)
    }
    func delete(at indexSet: IndexSet) {
        dogs.list.remove(atOffsets: indexSet)
    }
}

DogListRow 视图

struct DogListRow: View {
    @EnvironmentObject var user: User
    @ObservedObject var dog: Dog
    var body: some View {
        HStack(spacing: 10) {
            Image(uiImage: dog.image)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 80, height: 80)
                .clipShape(Circle())
                .padding()
            VStack(spacing: 4) {
                Text(dog.name)
                    .font(.title)
                    .lineLimit(2)
                if !dog.walkArray.isEmpty {
                    if let firstWalk = dog.walkArray.first {
                        if let time = firstWalk.time.timeToString() {
                            VStack {
                                Text("Latest Walk")
                                    .font(.footnote)
                                Text(time)
                            }
                        }
                    }
                }
            }
            VStack(spacing: 3) {
                if dog.walkArray.first?.firstAction == true {
                    Text("1")
                        .frame(width: 65, height: 35)
                        .background(user.profile.firstColor)
                        .cornerRadius(15)
                }
                if dog.walkArray.first?.secondAction == true {
                    Text("2")
                        .frame(width: 65, height: 35)
                        .background(user.profile.secondColor)
                        .cornerRadius(15)
                }
            }
        }
    }
}

NewDogView 制作新狗

struct NewDogView: View {
    @Environment(\.presentationMode) var presentation
    @EnvironmentObject var dogs: DogArray
    @State var alertItem: AlertItem?
    @State var dogCreated: Bool = false
    @State var name: String = ""
    @State var uiImage: UIImage? = nil
    @State var showAction: Bool = false
    @State var showImagePicker: Bool = false
    
    var body: some View {
        ScrollView(showsIndicators: false) {
            if (uiImage == nil) {
                Image(systemName: "camera.circle.fill")
                    .resizable()
                    .frame(width: 130, height: 130)
                    .onTapGesture {
                        displayImagePicker()
                    }
            } else {
                Image(uiImage: uiImage!)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 200, height: 200)
                    .cornerRadius(15)
                    .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
                    .onTapGesture {
                        displayActionSheet()
                    }
            }
            TextField("Enter the Dogs name", text: $name)
                .multilineTextAlignment(.center)
                .padding(EdgeInsets(top: 40, leading: 20, bottom: 20, trailing: 20))
        }
        .fixFlickering()
        .alert(item: $alertItem, content: { alertItem in
            Alert(title: alertItem.title,
                  message: alertItem.message,
                  dismissButton: .default(alertItem.buttonTitle))
        })
        .sheet(isPresented: $showImagePicker, onDismiss: {
            dismissImagePicker()
        }, content: {
            ImagePicker(presenting: $showImagePicker, uiImage: $uiImage)
        })
        .actionSheet(isPresented: $showAction) {
            sheet
        }
        Group {
            Button(action: {
                createDog(name: name, image: uiImage)
                if dogCreated {
                    self.presentation.wrappedValue.dismiss()
                }
            }, label: {
                Text("Add dog")
                    .frame(width: 120, height: 60)
                    .foregroundColor(.white)
            })
            .padding(.horizontal, 8).lineLimit(1).minimumScaleFactor(0.4)
            .background(Color.orange)
            .cornerRadius(30)
        }
    }
    private func createDog(name: String, image: UIImage? = nil) {
        if isDogValid(name: name, image: image) {
            dogs.addDog(dog: Dog(name: name, image: image!))
            dogCreated = true
        }
        else {
            dogCreated = false
        }
    }
    var sheet: ActionSheet {
        ActionSheet(
            title: Text("Action"),
            message: Text("Update Image"),
            buttons: [
                .default(Text("Change"), action: {
                    self.dismissActionSheet()
                    self.displayImagePicker()
                }),
                .cancel(Text("Close"), action: {
                    self.dismissActionSheet()
                }),
                .destructive(Text("Remove"), action: {
                    self.dismissActionSheet()
                    self.uiImage = nil
                })
            ])
    }
    
    func displayActionSheet() {
        showAction = true
    }
    
    func dismissActionSheet() {
        showAction = false
    }
    
    func displayImagePicker() {
        showImagePicker = true
    }
    
    func dismissImagePicker() {
        showImagePicker = false
    }
    
    func isDogValid(name: String, image: UIImage?) -> Bool {
        if name != "" && image != nil {
            return true
        }
        else if name == "" && image != nil  {
            alertItem = AlertContext.NewDog.noName
        }
        else if name != "" && image == nil {
            alertItem = AlertContext.NewDog.noImage
        }
        else {
            alertItem = AlertContext.NewDog.noNameNoImage
        }
        return false
    }
}

您可以在其中编辑按钮颜色的配置文件视图

struct ProfileView: View {
    @Environment(\.colorScheme) var colorScheme
    @EnvironmentObject var user: User
    @State var permProfile: Profile = Profile(walkColor: Color.orange, firstColor: Color.blue, secondColor: Color.pink)
    @State private var presentModal = false
    @State var selectedColor = SelectedColor.none
    
    let colors = [Color.red, Color.blue, Color.green, Color.orange, Color.pink, Color.yellow, Color.red, Color.white, Color.gray, Color.black]
    
    @ViewBuilder func ColorView(color: Color) -> some View {
        (color)
            .cornerRadius(10)
            .onTapGesture {
                switch selectedColor {
                case .walk:
                    permProfile.walkColor = color
                case .first:
                    permProfile.firstColor = color
                case .second:
                    permProfile.secondColor = color
                default: break
                }
            }
    }
    var body: some View {
        ZStack {
            VStack(spacing: 8) {
                Text("Edit Selection Items")
                    .font(.title)
                    .padding(.bottom)
                .padding(5)
                Text("Change the button color")
                    .font(.title3)
                    .padding(EdgeInsets(top: 12, leading: 0, bottom: 5, trailing: 0))
                HStack {
                    Button(action: {
                        presentModal = true
                        selectedColor = .first
                    }) {
                        Text("1")
                            .frame(width: 100, height: 50, alignment: .center)
                            .foregroundColor(.white)
                    }
                    .background(permProfile.firstColor)
                    .cornerRadius(30)
                    
                    Button(action: {
                        presentModal = true
                        selectedColor = .walk
                    }) {
                        Text("Walk")
                            .frame(width: 100, height: 50, alignment: .center)
                            .foregroundColor(.white)
                    }
                    .background(permProfile.walkColor)
                    .cornerRadius(30)
                    
                    Button(action: {
                        presentModal = true
                        selectedColor = .second
                    }) {
                        Text("2")
                            .frame(width: 100, height: 50, alignment: .center)
                            .foregroundColor(.white)
                    }
                    .background(permProfile.secondColor)
                    .cornerRadius(30)
                }
                Spacer()
            }
            .padding()
            if presentModal {
                ModalView() {
                    ScrollView {
                        VStack(spacing: 40) {
                            ForEach(colors, id: \.self) { color in
                                ColorView(color: color)
                                    .frame(minWidth: 20, maxWidth: .infinity, minHeight: 20, maxHeight: .infinity)
                                    .padding([.leading, .trailing], 20)
                            }
                        }
                    }
                } closeModal: {
                    presentModal = false
                }
            }
        }
        .onAppear() {
            permProfile = user.profile
        }
        .onDisappear() {
            user.profile = permProfile
        }
    }
}

在列表中选择狗时显示的 DogDetailView

struct DogDetailView: View {
    @ObservedObject var dog: Dog
    var body: some View { //MARK: View
        // Dog Section
        ZStack {
            VStack {
                HStack {
                    Image(uiImage: dog.image)
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                        .frame(width: 110, height: 110)
                        .clipShape(Circle())
                    VStack(alignment: .leading) {
                        Text(dog.name)
                            .font(.title)
                    }
                    .padding(.leading, 8)
                    Spacer()
                }
                .padding(EdgeInsets(top: 0, leading: 15, bottom: 8, trailing: 10))
                List {
                    ForEach(0..<dog.walkArray.count, id: \.self) { walk in
                        ZStack(alignment: .leading) {
                            TimeRow(walk: dog.walkArray[walk])
                        }
                    }
                }
                Group {
                    WalkSectionView(dog: dog)
                }
            }
        }
    }
}

【问题讨论】:

  • Dog 是一个类。 DogArray 中的 list 仅存储引用。当 Dog 的属性改变时,它的引用保持不变,list 不会改变,didSet 不会被调用。将Dog 设为struct 并使用Binding 作为您的DogDetailView。
  • 为你的 ViewModel 使用 class + @ObservableObject,而不是你的 Model。否则,您的 ViewModel 将无法收到模型更改的通知
  • @Adrien 对 Dog 对象使用结构是有意义的。你对 ViewModel 是什么意思?你的意思是我的模型不应该是@ObservablesObjects?我的 ViewModel 应该如何更新它的变化,它是否应该包含 Dog 和 User @EnvironmentObjects?
  • 我要写一个更长的答案。
  • @Adrien 欣赏!我可能错误地将我的属性包装器捆绑在一起,但是由于我希望 User 和 DogArray 可以全局访问,我认为它们应该是 EnvironmentObjects。据我了解绑定它应该主要用于处理父视图和子视图之间的更改,而不是几个视图。然后,我将如何在应用程序中的任何位置访问仍然更新 UI 和 UserDefaults 的对象?

标签: arrays swift xcode swiftui storage


【解决方案1】:

在您的项目中,Dog 是一个类。你的数组list,在你的DogArray 类中,只存储引用。当Dog 的属性发生变化时,它的引用保持不变,数组不变,didSet 也不会被调用。

我简化了您的项目以使问题更容易理解:

struct DogList: View {
    @StateObject var dogs = DogArray(list: ["John", "Bob"].map(Dog.init))
    var body: some View {
        NavigationView {
            List(dogs.list.indices) {index in
                NavigationLink(destination: DogDetail(dog: dogs.list[index])) {
                    Text(dogs.list[index].name)
                    Image(systemName: "heart.fill")
                        .foregroundColor(dogs.list[index].favorite ? .red : .gray)
                }
            }
        }
    }
}

struct DogDetail: View {
    @ObservedObject var dog: Dog
    var body: some View {
        VStack {
            Text(dog.name)
                
            Button {
                dog.favorite.toggle()
            } label: {
                Image(systemName: "heart.fill")
                    .foregroundColor(dog.favorite ? .red : .gray)
            }
        }.font(.largeTitle)
    }
}

class Dog: ObservableObject {
    let id = UUID()
    var name: String
    @Published var favorite = false
    init(_ name: String) {
        self.name = name
    }
}


class DogArray: ObservableObject {
    @Published var list: [Dog] {
        didSet {
            print("Array changes")
        }
    }
    init(list: [Dog]) {
        self.list = list
    }
}

这里,当用户在DogDetail 视图中点击心脏时,Dog 发生了变化,DogDetail 的身体被重绘(因为 Dog 是可观察的)。但DogList 并未失效。父视图不知道 Dog 发生了变化。

我们只需要做三件事来解决问题。

  1. 使Dog 成为struct
  2. @ObservedObject var dog: Dog -> @Binding var dog: Dog
  3. DogDetail(dog: dogs.list[index]) -> DogDetail(dog: $dogs.list[index])

现在可以了。

你的问题比这个例子复杂一点,因为你使用ObservableObject 来做三件事:你的模型(这里是Dog:要避免),你的商店(这里是DogArray)和一个ViewModel(我看不太清楚它的用处)。

您应该选择我认为在视图层次结构顶部使用 EnvironmentObject 注入的全局存储(您的DogArray)。需要它的观点得到它。都共享同一个对象。

或者真正切换到具有 ViewModel 的架构,在这种情况下,由他们每个人来管理对模型的访问。

可能是这样的:

import SwiftUI
struct DogList: View {
    class ViewModel: ObservableObject {
        @Published var list: [DogDetail.ViewModel]
        init(dogs: [Dog]) {
            list = []
            list = dogs.map { DogDetail.ViewModel(dog: $0, parent: self) }
        }
        func save() {
            print("list saved")
        }
    }
    @StateObject var vm = DogList.ViewModel(dogs:["John", "Bob"].map(Dog.init))
    var body: some View {
        NavigationView {
            List(vm.list, id: \.dog.id) {detail in
                NavigationLink(destination: DogDetail(vm: detail)) {
                    DogRow(vm: detail)
                }
            }
        }
    }
}

struct DogRow: View {
    @ObservedObject var vm: DogDetail.ViewModel
    var body: some View {
        Text(vm.dog.name)
        Image(systemName: "heart.fill")
            .foregroundColor(vm.dog.favorite ? .red : .gray)
    }
}

struct DogDetail: View {
    class ViewModel: ObservableObject {
        let parent: DogList.ViewModel
        @Published var dog: Dog {
            didSet {
                print("a dog changed")
                parent.objectWillChange.send()
                parent.save()
            }
        }
        init(dog: Dog, parent: DogList.ViewModel) {
            self.dog = dog
            self.parent = parent
        }
    }
    
    @ObservedObject var vm: ViewModel
    var dog: Dog { vm.dog }
    var body: some View {
        VStack {
            Text(dog.name)
            Button {
                vm.dog.favorite.toggle()
            } label: {
                Image(systemName: "heart.fill")
                    .foregroundColor(dog.favorite ? .red : .gray)
            }
        }.font(.largeTitle)
    }
}

编辑(合并)

您可能不喜欢“父母”将自己的引用指向其“孩子”:DogDetail.ViewModel(dog: $0, parent: self)

您还可以使用组合并让DogList.ViewModel 订阅DogDetail.ViewModel.dog 中的更改:

import Combine
struct DogList: View {
    class ViewModel: ObservableObject {
        @Published var list: [DogDetail.ViewModel] {
            didSet {
                subscribeToChanges()
            }
        }
        func subscribeToChanges() {
            self.cancellable = list.publisher
                .flatMap { $0.dogDidChange }
                .sink { [weak self] _ in
                    self?.objectWillChange.send()
                    self?.save()
                }
        }
        var cancellable: AnyCancellable?
        init(dogs: [Dog]) {
            list = dogs.map { DogDetail.ViewModel(dog: $0) }
            subscribeToChanges()
        }
        func save() {
            print("list saved :")
            for dogVM in list {
                print(dogVM.dog.favorite)
            }
        }
    }
// ....//
}

您需要DogDetail.ViewModel 中的dogDidChange 发布者:

struct DogDetail: View {
    class ViewModel: ObservableObject {
        var dogDidChange = PassthroughSubject<Void, Never>()
        @Published var dog: Dog {
            didSet {
                print("a dog changed")
                dogDidChange.send()
            }
        }
        init(dog: Dog) {
            self.dog = dog
        }
    }
//...//
}

【讨论】:

  • 很好的解释。我意识到我的 ViewModel 是非常不必要的,我认为我真正的问题是处理状态包装器以正确更新 User 和 DogArray 的父状态。我删除了所有虚拟机,并在每个视图中组合了所有逻辑。我还将 Profile 更改为 Struct,它现在存储对 Profile 所做的更改值。虽然我不确定如何在 Profile 对象更改时更新它,因为我目前必须在视图关闭时执行此操作。至于 Dog 对象,我不确定我应该如何在如此大的项目中处理结构。我用更改更新了我的问题和 git
  • @Kim :当您因为尝试了新事物而编辑问题时,请留下您的原始代码和问题,并将您的更改放在一个专门的段落中(例如在末尾)。否则我们不再理解发布的答案。您可以借此机会为对您有用的答案投票。
  • 确实如此,我将改变我的方法。此外,我只需要 1 个代表来支持,相信我,我已经尝试过很多次支持您的回复!
【解决方案2】:

当您添加 walk 时,只需将 dog 明确设置到下面的列表中,希望它能正常工作

Button(action: {
                dog.walk(time: Date(), firstSelect: dog.firstSelect, secondSelect: dog.secondSelect)
                dog.firstSelect = false
                dog.secondSelect = false
                dog.sortWalks()
                guard let i = dogData.list.firstIndex(where: {$0.id == dog.id})else{
                    return
                }
                dogData.list[i] = dog
                
            }, label: {
                Text("Walk")
                    .frame(width: 120, height: 60)
                    .foregroundColor(.white)
            })

【讨论】:

  • 如果模型是结构,它可能会起作用。但对我来说,这是一种不面对问题的方式。您不必搜索要修​​改的对象。我们有 @Binding 来处理这类事情。
  • @Adrien 你测试了吗?就我而言,它工作正常,因为实际数据已更新,而不仅仅是在列表中引用
  • 好的。我仍然认为我们不应该使用firstIndex。在这里它不会是性能问题,(用户不会管理数百万只狗,当它管理时会有其他问题 - 例如图像存储在 UserDefaults 中的事实)。但只是有了正确的架构,我们就不必这样做了。
猜你喜欢
  • 1970-01-01
  • 2015-08-09
  • 1970-01-01
  • 2019-07-28
  • 2023-03-12
  • 1970-01-01
  • 1970-01-01
  • 2017-08-30
  • 2011-08-04
相关资源
最近更新 更多