SwiftUI - 编辑 Codable 数组中项目的值不能在不可变值上使用可变成员
SwiftUI - Editing a value of an item in a Codable Array Cannot use mutating member on immutable value
我使用名为 Package
的 Codable
类型的数组建立了 List
。在某些时候,当用户点击列表中的 favorite
按钮时,我需要更新 Package
项的 isFavorite
值。但是我收到 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
}
}
最后我得到了错误,
将您的函数签名更改为
private func row(_ package: inout Package) -> some View
为什么有必要?因为当您将参数传递给函数时,该参数是一个 let
常量。 Package
是一个 struct
所以修改它的任何属性实际上意味着修改实例本身。你不能这样做,因为它是一个 let
常量。
另一件事是因为 Package
是一个 struct
这意味着当你将它传递给你的函数时,你实际上传递了一个 copy ,不是原来的值。
一个inout
参数有一个值被传递给函数,被函数修改,并传回函数外以替换原来的值。
很简单,您需要访问列表提供的值,而不是它的副本
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()
}
}
或使用 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()
}
}
这通常是相同的...但在更复杂的情况下更可取
根据要求更新
你必须重新定义行函数
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()
}
}
我使用名为 Package
的 Codable
类型的数组建立了 List
。在某些时候,当用户点击列表中的 favorite
按钮时,我需要更新 Package
项的 isFavorite
值。但是我收到 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
}
}
最后我得到了错误,
将您的函数签名更改为
private func row(_ package: inout Package) -> some View
为什么有必要?因为当您将参数传递给函数时,该参数是一个 let
常量。 Package
是一个 struct
所以修改它的任何属性实际上意味着修改实例本身。你不能这样做,因为它是一个 let
常量。
另一件事是因为 Package
是一个 struct
这意味着当你将它传递给你的函数时,你实际上传递了一个 copy ,不是原来的值。
一个inout
参数有一个值被传递给函数,被函数修改,并传回函数外以替换原来的值。
很简单,您需要访问列表提供的值,而不是它的副本
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()
}
}
或使用 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()
}
}
这通常是相同的...但在更复杂的情况下更可取
根据要求更新 你必须重新定义行函数
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()
}
}