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:
{
"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
}
}
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
}
}
}
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
}
}
}
}