Related
I'm trying to add in a support section (this was a demo that turned into something more) and thought that I could fetch a json file and add it into DisclosureGroup to the end user.
I originally thought that the issue was a network issue, but adding the file locally still caused the same problem.
When I run it in the simulator, and try to open one of the DisclosureGroup items, it doesn't open. If I they to press more the RAM usage increases but can't see a reason why it should be after the initial Bundle load into the array.
This is the data I was testing:
SupportQuestions.json
{
"sections": [
{
"title": "Section title 1",
"description": null,
"questions": [
{
"title": "Question title 1",
"response": "Answer 1"
},
{
"title": "Question title 3",
"response": "Answer 3"
}
]
},
{
"title": "Section title 2",
"description": "Section description",
"questions": [
{
"title": "Question title 4",
"response": "Answer 4"
},
{
"title": "Question title 5",
"response": "Answer 5"
},
{
"title": "Question title 6",
"response": "Answer 6"
}
]
},
{
"title": "Section title 3",
"description": "Another section description",
"questions": [
{
"title": "Question title 7",
"response": "Answer 7"
},
{
"title": "Question title 8",
"response": "Answer 8"
},
{
"title": "Question title 9",
"response": "Answer 9"
}
]
}
]
}
Then the Swift I was using in the View:
struct SettingsHelpView: View {
#State private
var suppportItems: [SupportSections.SupportCategory] = []
var body: some View {
Form {
ForEach(suppportItems) {
item in
Section {
ForEach(item.questions) {
question in
DisclosureGroup {
Text(question.response)
}
label: {
Text(question.title).bold()
}
}
}
header: {
Text(item.title)
}
footer: {
Text(item.decription ?? "")
}
}
}
.onAppear {
fetchHelpSection()
}
}
private func fetchHelpSection() {
let questions = Bundle.main.decode(SupportSections.self, from: "SupportQuestions.json")
suppportItems = questions.sections
}
}
Model
struct SupportSections: Decodable {
let sections: [SupportCategory]
struct SupportCategory: Decodable, Identifiable {
var id: String { UUID().uuidString }
let title: String
let decription: String?
let questions: [SupportQuestion]
struct SupportQuestion: Decodable, Identifiable {
var id: String { UUID().uuidString }
let title: String
let response: String
}
}
}
Bundle+Ext
extension Bundle {
func decode<T: Decodable>(_ type: T.Type, from file: String, dateDecodingStategy: JSONDecoder.DateDecodingStrategy = .deferredToDate, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> T {
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Error: Failed to locate \(file) in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Error: Failed to load \(file) from bundle.")
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = dateDecodingStategy
decoder.keyDecodingStrategy = keyDecodingStrategy
guard let loaded = try? decoder.decode(T.self, from: data) else {
fatalError("Error: Failed to decode \(file) from bundle.")
}
return loaded
}
}
Video of what is occurring (sorry don't know how to resize):
The issue comes from your id properties in your models. Right now, you have id defined as a computed property:
var id: String { UUID().uuidString }
This means that every time SwiftUI asks for an id, it gets a different value, since a new UUID is generated each time. This confuses SwiftUI and it 'closes' the DisclosureGroup because it thinks it's a new View (because of the new ID).
To fix this, declare your id properties as non-computed values and provide CodingKeys so that the system doesn't try to decode that property from the JSON file.
struct SupportSections: Decodable {
let sections: [SupportCategory]
struct SupportCategory: Decodable, Identifiable {
var id = UUID().uuidString //<-- Here
let title: String
let description: String? //note that you had a typo here in your original code
let questions: [SupportQuestion]
enum CodingKeys : String, CodingKey {
case title, description, questions
}
struct SupportQuestion: Decodable, Identifiable {
var id: String = UUID().uuidString //<-- Here
let title: String
let response: String
enum CodingKeys : String, CodingKey {
case title, response
}
}
}
}
I have an json array with a list of item, that has different property.
{
"items": [
{
"id": "1",
"name": "name",
"propertyOfA": "1243",
"productype": "a"
},
{
"id": "2",
"name": "name",
"propertyOfA": "12",
"productype": "a"
},
{
"id": "3",
"name": "name",
"propertyOfA": "1243",
"productype": "a"
},
{
"id": "1",
"name": "name",
"propertyOfB": "1243",
"productype": "b"
},
{
"id": "1",
"name": "name",
"propertyOfC": "1243",
"propertyOfC2": "1243",
"productype": "c"
}
]
}
What i usually do is parse it into something like :
struct RequestData: Codable {
let items: [Item]
}
enum ProductType: String, Codable {
case a = "A"
case b = "B"
case c = "C"
}
struct Item: Codable {
let id, name: String
let propertyOfA: String?
let productype: String
let propertyOfB: String?
let propertyOfC: String?
let propertyOfC2: String?
}
But if this array keep growing, its gonna end up with a Item with a huge amount of optional property,
so i want each object have its own dedicated class
adding some kind of bridge inside codable parsings,
what i want to archive is something like :
struct RequestData: Codable {
let items: Items
}
struct items: Codable {
let arrayA: [A]
let arrayB = [B]
let arrayC = [C]
}
struct A: Codable {
let id: String,
let name: String,
let propertyOfA: String,
let producttype: Productype
}
struct B {
...
}
struct C {
...
}
can i do this with Codable ?
A reasonable solution is an enum with associated values because the type can be determined by the productype key. The init method first decodes the productype with a CodingKey then in a switch it decodes (from a singleValueContainer) and associates the proper type/value to the corresponding case.
enum ProductType: String, Codable {
case a, b, c
}
struct Root : Codable {
let items : [Product]
}
struct ProductA : Codable {
let id, name: String
let productype: ProductType
let propertyOfA : String
}
struct ProductB : Codable {
let id, name: String
let productype: ProductType
let propertyOfB : String
}
struct ProductC : Codable {
let id, name: String
let productype: ProductType
let propertyOfC, propertyOfC2 : String
}
enum Product : Codable {
case a(ProductA), b(ProductB), c(ProductC)
enum CodingKeys : String, CodingKey { case productype }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(ProductType.self, forKey: .productype)
let singleContainer = try decoder.singleValueContainer()
switch type {
case .a : self = .a(try singleContainer.decode(ProductA.self))
case .b : self = .b(try singleContainer.decode(ProductB.self))
case .c : self = .c(try singleContainer.decode(ProductC.self))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .a(let productA): try container.encode(productA)
case .b(let productB): try container.encode(productB)
case .c(let productC): try container.encode(productC)
}
}
}
And decode
let jsonString = """
{
"items": [
{
"id": "1",
"name": "name",
"propertyOfA": "1243",
"productype": "a"
},
{
"id": "2",
"name": "name",
"propertyOfA": "12",
"productype": "a"
},
{
"id": "3",
"name": "name",
"propertyOfA": "1243",
"productype": "a"
},
{
"id": "1",
"name": "name",
"propertyOfB": "1243",
"productype": "b"
},
{
"id": "1",
"name": "name",
"propertyOfC": "1243",
"propertyOfC2": "1243",
"productype": "c"
}
]
}
"""
do {
let result = try JSONDecoder().decode(Root.self, from: Data(jsonString.utf8))
print(result)
} catch { print(error)}
To read the enum values use also a switch.
I am trying to fetch a random user from an api and I created a struct called result to match the json from the api.
This is what I came up with but I am sure this is not the right way to do it!
When I run this I get [] as a result so I want to know how I can change this code in order to match the json
#State var results : result?
struct result : Codable {
var results : [user]
struct user : Codable{
var gender : String
var name : name
var location : location
var email:String
struct name : Codable{
var title : String
var first : String
var last : String
}
struct location : Codable {
let street : street
let city : String
let state : String
let country : String
let postcode : Int
struct street :Codable {
var number : String
var name : String
}
}
struct login : Codable {
var uuid : String
var username : String
var password : String
}
}
}
This is how the Json looks
{
"results": [
{
"gender": "male",
"name": {
"title": "mr",
"first": "brad",
"last": "gibson"
},
"location": {
"street": "9278 new road",
"city": "kilcoole",
"state": "waterford",
"postcode": "93027",
"coordinates": {
"latitude": "20.9267",
"longitude": "-7.9310"
},
"timezone": {
"offset": "-3:30",
"description": "Newfoundland"
}
},
"email": "brad.gibson#example.com",
"login": {
"uuid": "155e77ee-ba6d-486f-95ce-0e0c0fb4b919",
"username": "silverswan131",
"password": "firewall",
"salt": "TQA1Gz7x",
"md5": "dc523cb313b63dfe5be2140b0c05b3bc",
"sha1": "7a4aa07d1bedcc6bcf4b7f8856643492c191540d",
"sha256": "74364e96174afa7d17ee52dd2c9c7a4651fe1254f471a78bda0190135dcd3480"
},
"dob": {
"date": "1993-07-20T09:44:18.674Z",
"age": 26
},
"registered": {
"date": "2002-05-21T10:59:49.966Z",
"age": 17
},
"phone": "011-962-7516",
"cell": "081-454-0666",
"id": {
"name": "PPS",
"value": "0390511T"
},
"picture": {
"large": "https://randomuser.me/api/portraits/men/75.jpg",
"medium": "https://randomuser.me/api/portraits/med/men/75.jpg",
"thumbnail": "https://randomuser.me/api/portraits/thumb/men/75.jpg"
},
"nat": "IE"
}
],
"info": {
"seed": "fea8be3e64777240",
"results": 1,
"page": 1,
"version": "1.3"
}
}
You want to learn how to do this on your own but there are tools on the web that can easily help you create a struct from a JSON. This is a really good one.
You were close so make sure you dissect the code and notice the differences
import SwiftUI
class ResultsViewModel: ObservableObject{
let json = """
{
"results": [
{
"gender": "male",
"name": {
"title": "mr",
"first": "brad",
"last": "gibson"
},
"location": {
"street": "9278 new road",
"city": "kilcoole",
"state": "waterford",
"postcode": "93027",
"coordinates": {
"latitude": "20.9267",
"longitude": "-7.9310"
},
"timezone": {
"offset": "-3:30",
"description": "Newfoundland"
}
},
"email": "brad.gibson#example.com",
"login": {
"uuid": "155e77ee-ba6d-486f-95ce-0e0c0fb4b919",
"username": "silverswan131",
"password": "firewall",
"salt": "TQA1Gz7x",
"md5": "dc523cb313b63dfe5be2140b0c05b3bc",
"sha1": "7a4aa07d1bedcc6bcf4b7f8856643492c191540d",
"sha256": "74364e96174afa7d17ee52dd2c9c7a4651fe1254f471a78bda0190135dcd3480"
},
"dob": {
"date": "1993-07-20T09:44:18.674Z",
"age": 26
},
"registered": {
"date": "2002-05-21T10:59:49.966Z",
"age": 17
},
"phone": "011-962-7516",
"cell": "081-454-0666",
"id": {
"name": "PPS",
"value": "0390511T"
},
"picture": {
"large": "https://randomuser.me/api/portraits/men/75.jpg",
"medium": "https://randomuser.me/api/portraits/med/men/75.jpg",
"thumbnail": "https://randomuser.me/api/portraits/thumb/men/75.jpg"
},
"nat": "IE"
}
],
"info": {
"seed": "fea8be3e64777240",
"results": 1,
"page": 1,
"version": "1.3"
}
}
""".data(using: .utf8)
var result: JSONObject? {
var decoded: JSONObject? = nil
do{
decoded = try JSONDecoder().decode(JSONObject.self, from: json!)
}catch{
print(error)
}
return decoded
}
}
struct ResultsView: View {
#StateObject var vm: ResultsViewModel = ResultsViewModel()
var body: some View {
if vm.result == nil || vm.result?.results.first == nil{
Text("nil")
}else{
VStack{
Text(vm.result!.results.first!.name.first)
Text(vm.result!.results.first!.name.last)
}
}
}
}
// MARK: - JSONObject
struct JSONObject: Codable {
let results: [Result]
let info: Info
}
// MARK: - Info
struct Info: Codable {
let seed: String
let results, page: Int
let version: String
}
// MARK: - Result
struct Result: Codable {
let gender: String
let name: Name
let location: Location
let email: String
let login: Login
let dob, registered: Dob
let phone, cell: String
let id: ID
let picture: Picture
let nat: String
}
// MARK: - Dob
struct Dob: Codable {
let date: String
let age: Int
}
// MARK: - ID
struct ID: Codable {
let name, value: String
}
// MARK: - Location
struct Location: Codable {
let street, city, state, postcode: String
let coordinates: Coordinates
let timezone: Timezone
}
// MARK: - Coordinates
struct Coordinates: Codable {
let latitude, longitude: String
}
// MARK: - Timezone
struct Timezone: Codable {
let offset, timezoneDescription: String
enum CodingKeys: String, CodingKey {
case offset
case timezoneDescription = "description"
}
}
// MARK: - Login
struct Login: Codable {
let uuid, username, password, salt: String
let md5, sha1, sha256: String
}
// MARK: - Name
struct Name: Codable {
let title, first, last: String
}
// MARK: - Picture
struct Picture: Codable {
let large, medium, thumbnail: String
}
This question already has answers here:
Sorting a Swift array by ordering from another array
(10 answers)
Closed 3 years ago.
In my json result, there is odds array. And inside of it, there will be 8 types of bet. And i want to sort the array by bet type as i want to show. Eg. first "name": "3Way Result", second another one, third "over/under" etc..
Here is my json result from server.
{
"success": true,
"result": [
{
"league_id": 5,
"localTeam": {"data": {}},
"visitorTeam": {"data": {}},
"scores": {},
"time": {"starting_at": {},},
"league": {"data": {"coverage": {}}},
"odds": [
{
"id": 12,
"name": "Over/Under",
"suspended": false,
"bookmaker": {
"data": [
{
"id": 2,
"name": "bet365",
"odds": {
"data": [
{
"label": "Over",
"value": "2.00",
"extra": null,
"probability": "50%",
"dp3": "2.000",
"american": 100,
"handicap": null,
"total": "2.5",
"winning": null,
"stop": false,
"bookmaker_event_id": 84922729,
},
{
"label": "Under",
"value": "1.80",
"probability": "55.56%",
"dp3": "1.800",
}
]
}
}
]
}
},
{
"id": 1,
"name": "3Way Result",
"suspended": false,
"bookmaker": {
"data": [
{
"id": 2,
"name": "bet365",
"odds": {
"data": [
{
"label": "1",
"value": "2.10",
"extra": null,
"probability": "47.62%",
"dp3": "2.100",
"american": 110,
},
{
"label": "X",
"value": "3.30",
"extra": null,
"probability": "30.3%",
"dp3": "3.300",
"american": 229,
},
{
"label": "2",
"value": "3.60",
}
]
}
}
]
}
},
{
"id": 975909,
"name": "Correct Score",
"suspended": false,
"bookmaker": {
"data": [
{
"id": 2,
"name": "bet365",
"odds": {
"data": [
{
"label": "1:0",
"value": "7.50",
"extra": null,
"probability": "13.33%",
"dp3": "7.500",
"american": 650,
"factional": null,
"handicap": null,
"total": null,
"winning": null,
"stop": false,
"bookmaker_event_id": 84922729,
"last_update": {
"date": "2020-02-20 10:59:06.746514",
"timezone_type": 3,
"timezone": "UTC"
}
},
{
"label": "2:0",
"value": "10.00",
},
]
}
}
]
}
},
],
"tipsters": 2
}
]
}
so it is not alphabetically, datetime or anytype i could access. How could i do ?
Update: I have added model.
struct BetterMatchResults: APIModel, Codable {
var success: Bool?
var result: [BetterMatch]?
}
struct BetterMatch: APIModel, Codable {
var id,_id: String?
var localTeam, visitorTeam: BetterTeam?
var spId, league_id, seasonID: Int?
var winningOddsCalculated: Bool?
var time: BetterTime?
var league: BetterLeague?
var createdAt, updatedAt: String?
var odds: [BetterOdd]!
var resultID: String?
var tipsters: Int?
var stats_url: String?
}
struct BetterLeague : APIModel, Codable {
var data: LeagueData?
}
struct LeagueData : APIModel, Codable{
var id: Int?
var active: Bool?
//var legacyID, countryID: Int?
var logo_path: String?
var name: String?
//var isCup: Bool?
}
struct BetterOdd : APIModel, Codable {
var id: Int?
var name: String?
var suspended: Bool?
var bookmaker: BetterBookmaker?
}
// MARK: - Bookmaker
struct BetterBookmaker : APIModel, Codable {
var data: [BetterBookmakerDatum]?
}
// MARK: - BookmakerDatum
struct BetterBookmakerDatum : APIModel, Codable {
var id: Int?
var name: String?
var odds: BetterOdds?
}
// MARK: - Odds
struct BetterOdds : APIModel, Codable {
var data: [BetterOddsDatum]?
}
class BetterOddsDatum: APIModel , Codable {
var label: String?
//var extra: NSNull?
//var probability, dp3: String?
var american: Int?
//var factional, handicap: NSNull?
var total: String?
var winning: Bool?
var stop: Bool?
var bookmakerEventID: Int?
//private var odd: Double
public var value: String?
init() {
}
}
If I understand your question correctly you want to be able sort the data based on betting type but the value of betting types are not sortable if used as a String variable. The solution would be to converting them into enum types with raw values and then sorting the array based on those raw values. Here is an example:
// Create a BetType for your datas
enum BetType: Int {
case overUnder = 0
case threeWayResult = 1 // 3WayResult
...
}
// Update your BetterOdd
struct BetterOdd : APIModel, Codable {
var id: Int?
var name: String?
var betType: BetType = .overUnder // or put your default value here
var suspended: Bool?
var bookmaker: BetterBookmaker?
}
// Loop your BetterMatch property's odds value after fetching datas.
for i in 0..<betterMatch.odds {
if betterMatch.odds[i].name == "over/under" {
betterMatch.odds[i].betType = .overUnder
}
... // Do the same for other types as well in else if blocks
}
Another alternative would be to add a function for getting the type in BetterOdd
struct BetterOdd ... {
... // Your properties
func getBetType() -> BetType {
if name == "over/under" {
return .overUnder
} else if name == "3WayResult" {
return .threeWayResult
}
... // Other cases
}
}
Finnaly for sorting you can do:
let result = betterMatch.odds.sorted({ $0.betType.rawValue > $1.betType.rawValue })
// or if you used the function solution
let result = betterMatch.odds.sorted({ $0.getBetType().rawValue > $1.getBetType().rawValue })
Since you are using a Codable approach you might need to loop the array and set the betType values based on name values.
i changed model
struct BetterOdd : APIModel, Codable {
var id: Int?
var name: String?
var suspended: Bool?
var bookmaker: BetterBookmaker?
//var betType: BetType = .threeWayResult
enum BetType:Int, APIModel, Codable {
case threeWayResult = 7
case overUnder = 6
case doubleChance = 5
case bothTeamsToScore = 4
case threeWayResultFirstHalf = 3
case threeWayResultSecondHalf = 2
case correctScore = 1
case hTfTdouble = 0
}
//
func getBetType() -> BetType {
if name == "3Way Result" {
return .threeWayResult
} else if name == "Over/Under" {
return .overUnder
} else if name == "Double Chance" {
return .doubleChance
} else if name == "Both Teams To Score" {
return .bothTeamsToScore
} else if name == "3Way Result 1st Half" {
return .threeWayResultFirstHalf
} else if name == "3Way Result 2nd Half"{
return .threeWayResultSecondHalf
} else if name == "Correct Score"{
return .correctScore
} else if name == "HF/FT Double" {
return .hTfTdouble
} else {
return .correctScore
}
}
}
and then :
let matchOddsArray = match.odds!
let result = matchOddsArray.sorted(by: { $0.betType.rawValue > $1.betType.rawValue})
let bet = result[indexPath.row]
works perfect.
First of all, mark result in BetterMatchResults and odds in BetterMatch as var, i.e.
struct BetterMatchResults: Codable {
let success: Bool
var result: [BetterMatch] //here....
}
struct BetterMatch: Codable {
var odds: [BetterOdd] //here....
//rest of the parameters...
}
Next, while parsing your JSON data, use map(_:) and sorted(by:) to modify the result and odds in the betterMatchResults, i.e.
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
var betterMatchResults = try JSONDecoder().decode(BetterMatchResults.self, from: data)
betterMatchResults.result = betterMatchResults.result.map {(betterMatch) in
var match = betterMatch
match.odds = match.odds.sorted(by: { $0.name < $1.name })
return match
}
//Test the response...
print(betterMatchResults.result.map({ $0.odds.map({ $0.name }) })) //[["3Way Result", "Correct Score", "Over/Under"]]
} catch {
print(error)
}
I can not understand what could be the problem
i have some Json
for example is
[
{
"id": 1,
"title": "Title",
"info": "info",
"brand": "brand",
"model": "MODEL",
"make_year": year,
"properties": [
{
"id": 1,
"icon": "ic_rgb",
"label": "color",
"value": "red"
},
{
"id": 2,
"icon": "ic_car",
"label": "type",
"value": "value"
},
{ "id": 3,
"icon": "ic_fuel",
"label": "fuel",
"value": "gas"
}
],
},
{
"id": 2,
"title": "title2",
"message": "massage2",
"info": "wow you are amazing, thanks for the help",
"properties": [
{
"id": 11,
"icon": "ic_rgb",
"label": "2color",
"value": "2blue"
},
{
"id": 21,
"icon": "ic_car",
"label": "2type",
"value": "2cgh"
},
{ "id": 31,
"icon": "ic_fuel",
"label": "fuel",
"value": "test"
},
....
],
}
]
and my model
struct Unicards: Hashable, Codable, Identifiable {
var id: Int
var title: String?
var info: String?
var brand: String?
var model: String?
var make_year: Int?
var messege: String?
var messege_color: String?
var price: String?
var price_currency: String?
var price_tooltip: String?
var properties: [HPropert]?
struct HPropert: Hashable, Codable, Identifiable {
var id: Int
var icon: String?
var label: String
var value: String
}
and what the problem... i'm trying to create forEach method for horizontal collection for properties
I partially succeeded, I implemented this method with a nested array, and it even generates the number of cells correctly. wow!)
but when I want to assign values for the text by index it gives an error ...
I actually can not understand the problem, since the array has already been calculated and created by the number of cell objects.
but when I ask the formula to assign values I get
Cannot convert value of type '[Unicards.HPropert]' to expected argument type 'Range<Int>'
my code below
struct Card_cell_rowOfCheracteristics: View {
var data2: Unicards
var body: some View {
let properties = data2.properties!
return VStack() {
// Text("thanx bro!. have a nice day")
// .font(.system(size: 14))
// .font(.headline)
// .fontWeight(.light)
// .multilineTextAlignment(.leading)
ScrollView(Axis.Set.horizontal) {
HStack {
ForEach(properties) { card in <--- Cannot convert value of type '[Unicards.HPropert]' to expected argument type 'Range<Int>'
VStack {
Image()
.resizable()
.scaledToFit()
.frame(width: 34, height: 34)
VStack {
Text(properties[card].label)
.font(.system(size: 14))
.fontWeight(.medium)
Text(properties[card].value)
.font(.system(size: 14))
}
}
}
}
}
}
}
struct Card_cell_rowOfCheracteristics_Previews: PreviewProvider {
static var previews: some View {
Card_cell_rowOfCheracteristics(data2: PlatesData[0])
}
}
}
but if i say
properties[0].label
no errors occur
This error indicates your data2.properties!'s type does not conform to the Identifiable protocol.
Say, if your data2.properties!'s type is A, then try to do:
struct A: Identifiable {
...
var id: String {
return /*some ID*/
}
...
}
This should fix your problem "behind the hood".
Try doing something like this:
ForEach(properties) { card in
.
Text(card?.label)
.
}
I think the issue is that each element in the array s referred as a card when you are iterating through the array. So to get the properties of that element in the array you would do so by: card?.[property_name]
As #UndergroundFox pointed, one way of solving this is conforming to Identifiable protocol. Another is to specify in the ForEach one key to serve as unique identifier, in my case I used a date that I know is unique (because I have one entry per day):
ForEach(arPictures, id: \.date) { picture in