如何在切换 SwiftUI 中使用 json 属性?
How to use json property in a toggle SwiftUI?
我尝试动态编程一个切换开关以在我的 SwiftUI 项目中显示较短的列表。
我使用的JSON文件是here.
我的项目在 GitHub 上。
我这样设置我的开关,但是当我打开它时,它没有显示正确的列表。目标是 link JSON isShow 属性到 toggle 属性。当toggle关闭时,将显示完整列表,当toggle打开时,将仅显示isShow=FALSE列表。
ForEach(0..<self.userData.movies.filter{ self.showShowOnly == true ? [=12=].isShow == self.showShowOnly : true }.count, id: \.self) { movieIndex in
VStack (alignment: .leading) {
NavigationLink(destination: ContentView(movie: self.$userData.movies[movieIndex])) {
Text(self.userData.movies[movieIndex].nom)
}
}}
电影结构是
struct Movie: Decodable, Encodable, Identifiable {
public var id: Int
public var nom: String
public var idL: String
public var note: Int
public var isShow: Bool
enum CodingKeys: String, CodingKey {
case id = "id"
case nom = "nom"
case idL = "idL"
case note = "note"
case isShow = "isShow"
}
}
json 提取器是:
public class MovieFetcher: ObservableObject {
func save(movies: [Movie]) {
OperationQueue().addOperation {
do {
let encoded = try JSONEncoder().encode(movies)
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
do {
try encoded.write(to: url[0].appendingPathComponent("movies"))
print("saved to ", url[0])
} catch {
print(error)
}
} catch {
print(error)
}
}
}
func load(completion: @escaping (([Movie]?) -> () )) {
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
do {
if let data = try? Data(contentsOf: urls[0].appendingPathComponent("movies")) {
let decodedLists = try JSONDecoder().decode([Movie].self, from: data as Data)
DispatchQueue.main.async {
completion(decodedLists)
}
} else {
loadFromJson(completion: completion)
}
} catch {
print(error)
completion(nil)
}
}
func loadFromJson(completion: @escaping (([Movie]?) -> () )) {
let url = URL(string: "https://gist.githubusercontent.com/thomjlg/0782e9e8e27c346af3600bff9923f294/raw/9705fb0c6b40eae59578755b86e331bea257972b/films2.json")!
URLSession.shared.dataTask(with: url) {(data,response,error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode([Movie].self, from: d)
DispatchQueue.main.async {
completion(decodedLists)
}
}else {
print("No Data")
completion(nil)
}
} catch {
print ("Error")
completion(nil)
}
}.resume()
}
}
有人知道如何更正我的代码吗?
谢谢
如果我对你的问题理解正确,你想调整过滤器。如果是正确的,试试这个过滤器:
ForEach(0..<self.userData.movies.filter{ self.showShowOnly ? [=10=].isShow == !self.showShowOnly : true }.count, id: \.self)
当 self.showShowOnly = false 时,这将为您提供完整列表。
当 self.showShowOnly = true 时,将为您提供所有带 "isShow=false" 的电影。
过滤器代码有效。请注意,在 323 部电影中,只有 1 部电影 "isShow"=true。
"nom":"Ratatouille",
"id":217,
"idL":"R",
"note":0,
"isShow":true
问题是在 ForEach 循环中使用 "movieIndex" 的方式。 "movieIndex" 指的是过滤后的 self.userData.movies 列表的索引。这个"movieIndex"
不应在 self.userData.movies[movieIndex] 中使用。
解决这个问题:
ForEach(self.userData.movies.filter{ self.showShowOnly ? [=11=].isShow == !self.showShowOnly : true }) { movie in
VStack (alignment: .leading) {
NavigationLink(destination: ContentView(movie: movie)) {
Text(movie.nom)
}
}
}
试试这个,如果有效请告诉我。
编辑:试试这个 class 电影而不是结构
open class Movie: Codable, Identifiable, Equatable, Hashable, ObservableObject {
@Published public var id: Int
@Published public var nom: String
@Published public var idL: String
@Published public var note: Int
@Published public var isShow: Bool
enum CodingKeys: String, CodingKey {
case id = "id"
case nom = "nom"
case idL = "idL"
case note = "note"
case isShow = "isShow"
}
public static func == (lhs: Movie, rhs: Movie) -> Bool {
lhs.id == rhs.id
}
public func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
public init(id: Int, nom: String, idL: String, note: Int, isShow: Bool) {
self.id = id
self.nom = nom
self.idL = idL
self.note = note
self.isShow = isShow
}
public init() {
self.id = UUID().hashValue
self.nom = ""
self.idL = ""
self.note = 0
self.isShow = false
}
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.nom = try container.decode(String.self, forKey: .nom)
self.idL = try container.decode(String.self, forKey: .idL)
self.note = try container.decode(Int.self, forKey: .note)
self.isShow = try container.decode(Bool.self, forKey: .isShow)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(nom, forKey: .nom)
try container.encode(idL, forKey: .idL)
try container.encode(note, forKey: .note)
try container.encode(isShow, forKey: .isShow)
}
}
请注意,您必须在 ContentView 中将“@Binding var movie : Movie”更改为“@ObservedObject var movie: Movie”并调整 "ContentView_Previews"。
我尝试动态编程一个切换开关以在我的 SwiftUI 项目中显示较短的列表。
我使用的JSON文件是here.
我的项目在 GitHub 上。
我这样设置我的开关,但是当我打开它时,它没有显示正确的列表。目标是 link JSON isShow 属性到 toggle 属性。当toggle关闭时,将显示完整列表,当toggle打开时,将仅显示isShow=FALSE列表。
ForEach(0..<self.userData.movies.filter{ self.showShowOnly == true ? [=12=].isShow == self.showShowOnly : true }.count, id: \.self) { movieIndex in
VStack (alignment: .leading) {
NavigationLink(destination: ContentView(movie: self.$userData.movies[movieIndex])) {
Text(self.userData.movies[movieIndex].nom)
}
}}
电影结构是
struct Movie: Decodable, Encodable, Identifiable {
public var id: Int
public var nom: String
public var idL: String
public var note: Int
public var isShow: Bool
enum CodingKeys: String, CodingKey {
case id = "id"
case nom = "nom"
case idL = "idL"
case note = "note"
case isShow = "isShow"
}
}
json 提取器是:
public class MovieFetcher: ObservableObject {
func save(movies: [Movie]) {
OperationQueue().addOperation {
do {
let encoded = try JSONEncoder().encode(movies)
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
do {
try encoded.write(to: url[0].appendingPathComponent("movies"))
print("saved to ", url[0])
} catch {
print(error)
}
} catch {
print(error)
}
}
}
func load(completion: @escaping (([Movie]?) -> () )) {
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
do {
if let data = try? Data(contentsOf: urls[0].appendingPathComponent("movies")) {
let decodedLists = try JSONDecoder().decode([Movie].self, from: data as Data)
DispatchQueue.main.async {
completion(decodedLists)
}
} else {
loadFromJson(completion: completion)
}
} catch {
print(error)
completion(nil)
}
}
func loadFromJson(completion: @escaping (([Movie]?) -> () )) {
let url = URL(string: "https://gist.githubusercontent.com/thomjlg/0782e9e8e27c346af3600bff9923f294/raw/9705fb0c6b40eae59578755b86e331bea257972b/films2.json")!
URLSession.shared.dataTask(with: url) {(data,response,error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode([Movie].self, from: d)
DispatchQueue.main.async {
completion(decodedLists)
}
}else {
print("No Data")
completion(nil)
}
} catch {
print ("Error")
completion(nil)
}
}.resume()
}
}
有人知道如何更正我的代码吗?
谢谢
如果我对你的问题理解正确,你想调整过滤器。如果是正确的,试试这个过滤器:
ForEach(0..<self.userData.movies.filter{ self.showShowOnly ? [=10=].isShow == !self.showShowOnly : true }.count, id: \.self)
当 self.showShowOnly = false 时,这将为您提供完整列表。
当 self.showShowOnly = true 时,将为您提供所有带 "isShow=false" 的电影。
过滤器代码有效。请注意,在 323 部电影中,只有 1 部电影 "isShow"=true。
"nom":"Ratatouille",
"id":217,
"idL":"R",
"note":0,
"isShow":true
问题是在 ForEach 循环中使用 "movieIndex" 的方式。 "movieIndex" 指的是过滤后的 self.userData.movies 列表的索引。这个"movieIndex" 不应在 self.userData.movies[movieIndex] 中使用。
解决这个问题:
ForEach(self.userData.movies.filter{ self.showShowOnly ? [=11=].isShow == !self.showShowOnly : true }) { movie in
VStack (alignment: .leading) {
NavigationLink(destination: ContentView(movie: movie)) {
Text(movie.nom)
}
}
}
试试这个,如果有效请告诉我。
编辑:试试这个 class 电影而不是结构
open class Movie: Codable, Identifiable, Equatable, Hashable, ObservableObject {
@Published public var id: Int
@Published public var nom: String
@Published public var idL: String
@Published public var note: Int
@Published public var isShow: Bool
enum CodingKeys: String, CodingKey {
case id = "id"
case nom = "nom"
case idL = "idL"
case note = "note"
case isShow = "isShow"
}
public static func == (lhs: Movie, rhs: Movie) -> Bool {
lhs.id == rhs.id
}
public func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
public init(id: Int, nom: String, idL: String, note: Int, isShow: Bool) {
self.id = id
self.nom = nom
self.idL = idL
self.note = note
self.isShow = isShow
}
public init() {
self.id = UUID().hashValue
self.nom = ""
self.idL = ""
self.note = 0
self.isShow = false
}
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.nom = try container.decode(String.self, forKey: .nom)
self.idL = try container.decode(String.self, forKey: .idL)
self.note = try container.decode(Int.self, forKey: .note)
self.isShow = try container.decode(Bool.self, forKey: .isShow)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(nom, forKey: .nom)
try container.encode(idL, forKey: .idL)
try container.encode(note, forKey: .note)
try container.encode(isShow, forKey: .isShow)
}
}
请注意,您必须在 ContentView 中将“@Binding var movie : Movie”更改为“@ObservedObject var movie: Movie”并调整 "ContentView_Previews"。