SwiftUI - Редактирование значения элемента в массиве Codable. Невозможно использовать мутирующий элемент для неизменного значения. - PullRequest
0 голосов
/ 20 февраля 2020

У меня есть сборка List с использованием массива типа Codable с именем Package. В какой-то момент мне нужно обновить значение isFavorite элемента Package, когда пользователь нажимает кнопку favorite в списке. Однако я получаю ошибку Cannot use mutating member on immutable value: 'package' is a 'let' constant. Сначала я попытался напрямую изменить значение в действии. Это не сработало, поэтому пункт, который я получил, находится ниже.

Очевидно, что код длиннее этого, но я просто хотел сделать его простым и удалил ненужные части.

SwiftUIView

struct AView: View {

    @ObservedObject var store: JSONController

    var body: some View {
        NavigationView {
            List(self.store.packages) { (package) in
                return self.row(package)
            }
        }
    }

    private func row(_ package: Package) -> some View {
        HStack{
            Text(package.name)
            Spacer()
            Button(action: {
                package.changeFavoriteState()
            }) {
                Image(systemName: package.isFavorite ? "star.fill" : "star")
            }.buttonStyle(PlainButtonStyle())
        }
    }
}

Package.struct

struct Package: Codable, Identifiable {

    var id: UUID
    var isFavorite: Bool

    mutating func changeFavoriteState() {
        self.isFavorite.toggle()
    }
}

PackageList.struct

struct PackageList: Codable {

    var packages: [Package]

    private enum CodingKeys: CodingKey {
        case packages
    }

    init(){
        self.packages = [Package]()
    }

    init(from decoder: Decoder) throws {
        do {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            self.packages = try container.decode([Package].self, forKey: .packages)
        }catch let error {
            print(error.localizedDescription+" Initializing packages with an empty array.")
            self.packages = [Package]()
        }
    }
}

JSONController.class

class JSONController: ObservableObject {

    @Published var packages: [Package] = [Package]()

    init(){
        self.readJSONFile(from: "packageList")
    }

    func readJSONFile(from url: String) {
        //stuff
    }

}

И, наконец, ошибка, которую я получил,

error

Ответы [ 2 ]

1 голос
/ 20 февраля 2020

это просто, вам нужен доступ к значению, а не к его копии, предоставленной List

import SwiftUI

struct Package {
    var flag: Bool
}

struct ContentView: View {
    @State var arr = [Package(flag: true), Package(flag: false), Package(flag: true)]
    var body: some View {
        List(arr.indices, id:\.self) { (index) in
            Text(self.arr[index].flag.description).onTapGesture {
                self.arr[index].flag.toggle()
            }
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

enter image description here

или с помощью ObservableObject

import SwiftUI

struct Package: Identifiable {
    let id = UUID()
    var flag: Bool
}
class Model: ObservableObject {
    @Published var arr = [Package(flag: true), Package(flag: false), Package(flag: true)]
}

struct ContentView: View {
    @ObservedObject var model = Model()
    var body: some View {
        List(model.arr.indices) { (idx) in
            Text(self.model.arr[idx].flag.description)
                .onTapGesture {
                    self.model.arr[idx].flag.toggle()
            }
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

, который, как правило, тот же ... но в более сложном сценарии ios предпочтительнее

ОБНОВЛЕНИЕ на основе запроса Вы должны переопределить функцию строки

import SwiftUI

struct Package: Identifiable {
    let id = UUID()
    var flag: Bool
}
class Model: ObservableObject {
    @Published var arr = [Package(flag: true), Package(flag: false), Package(flag: true)]
}

struct ContentView: View {
    @ObservedObject var model = Model()
    var body: some View {
        List(model.arr.indices) { (idx) in
            self.row(flag: self.$model.arr[idx].flag)
        }
    }

    func row(flag: Binding<Bool>)-> some View {
        Text(flag.wrappedValue.description)
            .onTapGesture {
                flag.wrappedValue.toggle()
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Или второй вариант

import SwiftUI

struct Package: Identifiable {
    let id = UUID()
    var flag: Bool
}
class Model: ObservableObject {
    @Published var arr = [Package(flag: true), Package(flag: false), Package(flag: true)]
}

struct ContentView: View {
    @ObservedObject var model = Model()
    var body: some View {
        List(model.arr.indices) { (idx) in
            self.row(idx: idx)
        }
    }

    func row(idx: Int)-> some View {
        Text(model.arr[idx].flag.description)
            .onTapGesture {
                self.model.arr[idx].flag.toggle()
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

ОБНОВЛЕНИЕ с условной сортировкой

import SwiftUI

struct Package: Identifiable {
    let id: Int
    var flag: Bool
}
class Model: ObservableObject {
    @Published var arr = [Package(id: 1, flag: true), Package(id: 3, flag: false), Package(id: 2, flag: true)]
    @Published var ascending = false

    var sorted: [Package] {
        arr.sorted { (p0, p1) -> Bool in
            let sort = (p0.id < p1.id)
            return ascending ? !sort : sort
        }
    }
}

struct ContentView: View {
    @ObservedObject var model = Model()

    var body: some View {

        VStack {
            Toggle(isOn: $model.ascending) {
                Text("ascending")
            }.padding(.horizontal)

            List(model.sorted.indices, id: \.self) { (idx) in
                self.row(idx: idx)
            }
        }
    }

    func row(idx: Int)-> some View {
        Text(verbatim: model.sorted[idx].flag.description + ": " + model.sorted[idx].id.description)
            .onTapGesture {
                if let i = self.model.arr.firstIndex (where: { (p) -> Bool in
                    self.model.sorted[idx].id == p.id
                }) {
                    self.model.arr[i].flag.toggle()
                }
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
1 голос
/ 20 февраля 2020

Измените подпись вашей функции на

private func row(_ package: inout Package) -> some View

Зачем это нужно? Потому что, когда вы передаете параметр в функцию, этот параметр является константой let. Package - это struct, поэтому изменение любого из его свойств означает изменение самого экземпляра. Что вы не можете сделать, потому что это let константа.

Другое дело, что поскольку Package является struct, это означает, что когда вы передаете его своей функции, вы фактически передаете копию , а не исходное значение.

Параметр inout имеет значение, которое передается в функцию, модифицируется функцией и передается обратно из функции для замены исходного значения.

...