UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Expanding List View from JSON

Forums > SwiftUI

Background As the title says, I'd like to make an expanding list view (like Paul does in the previous link) but with JSON instead of hardcoded data. In both his article and video on the subject, he says it's too messy with JSON - I'm not even sure if this is something that I should be pursuing or if it's just a waste of effort.

I'm using the iDine tutorial as a template. This is the source code (this will download the original iDine project to your computer - this link is found on the previous link's page about halfway down).

Basically, it is a resturant app, the menu is JSON. It is a nested JSON menu with 2 layers; contents of the first layer are the MenuSections (ex. breakfast) and the second layer are the MenuItems (ex. pancakes) - MenuSection and MenuItem are the structs inside the app to decode the JSON. I'd like to make all MenuItems (pancakes) collapse under MenuSection (breakfast).

Here is what the original structs look like:

import SwiftUI

struct MenuSection: Codable, Identifiable {
    var id: UUID
    var name: String
    var items: [MenuItem]
}

struct MenuItem: Codable, Equatable, Identifiable {
    var id: UUID
    var name: String
    var photoCredit: String
    var price: Int
    var restrictions: [String]
    var description: String

    var mainImage: String {
        name.replacingOccurrences(of: " ", with: "-").lowercased()
    }

    var thumbnailImage: String {
        "\(mainImage)-thumb"
    }
}

Following Paul's example I've eliminated MenuSection and added an items variable to MenuItem. The items variable contains an optional array of MenuItem - i.e. the same type as the struct (exactly what Paul does in his article). Example:

import SwiftUI

//struct MenuSection: Codable, Identifiable {
//    var id: UUID
//    var name: String
//    var items: [MenuItem]
//}

struct MenuItem: Codable, Equatable, Identifiable {
    var id: UUID
    var name: String
    var items: [MenuItem]?  // <-----new variable
    var photoCredit: String
    var price: Int
    var restrictions: [String]
    var description: String

    var mainImage: String {
        name.replacingOccurrences(of: " ", with: "-").lowercased()
    }

    var thumbnailImage: String {
        "\(mainImage)-thumb"
    }
}

Problem

When I try to run the app, the build succeeds but the app then crashes because it cannot decode the JSON. It returns an error keyNotFound(CodingKeys(stringValue: "photoCredit", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"photoCredit\", intValue: nil) (\"photoCredit\").", underlyingError: nil)).

If I'm interpreting this correctly, XCode is complaining that there is no value photoCredit in the top layer of my JSON (which used to be MenuSection but is now just another instance of MenuItem - JSON seen below).

Am I wrong to assume that if XCode doesn't find a matching value for a particular instance of a struct inside the associated JSON that it will just ignore it? I thought it would ignore it because in several other cases I've just had missing image files that would associate with var mainImage or var thumbnailImage if they were there but it never caused any issue - the images just never showed up.

Code

Here is a portion of the JSON menu for reference:

[
    {
        "id": "EF1CC5BB-4785-4D8E-AB98-5FA4E00B6A66",
        "name": "Breakfast",
        "items": [
            {
                "id": "EDCD038C-036F-4C40-826F-61C88CD84DDD",
                "name": "Maple French Toast",
                "photoCredit": "Joseph Gonzalez",
                "price": 6,
                "restrictions": ["G", "V"],
                "description": "Sweet, fluffy, and served piping hot, our French toast is flown in fresh every day from Maple City, Canada, which is where all maple syrup in the world comes from. And if you believe that, we have some land to sell you…"
            },
            {
                "id": "36A7CC40-18C1-48E5-BCD8-3B42D43BEAEE",
                "name": "Stack-o-Pancakes",
                "photoCredit": "Joseph Gonzalez",
                "price": 8,
                "restrictions": ["D", "G", "V"],
                "description": "What do you get? Pancakes! How many do you get? One metric stack! That's equivalent to 1000 millistacks, or a tenth of a kilostack. In short, you get a lot of pancakes."
            },
            {
                "id": "CFB8C2DB-3478-4A10-92FA-3D2A2D5324FB",
                "name": "Power Muesli",
                "photoCredit": "Hanny Naibaho",
                "price": 5,
                "restrictions": ["D", "N"],
                "description": "Is normal muesli too boring? Of course it is. That's why we serve power muesli – it's like regular muesli, except we put the word \"power\" in front of it so we can charge more."
            },
            {
                "id": "8903A204-F5C1-4D5C-A0A1-5C0ECD27FDD7",
                "name": "Fresh-baked Croissant",
                "photoCredit": "Kavita Joshi Rai",
                "price": 3,
                "restrictions": ["D", "G"],
                "description": "This is just a regular croissant that we buy in bulk from a frozen foods supermarket, but we needed some sort of adjective to justify our price. So, it was either \"fresh-baked\" or \"authentic Parisian\", and our legal team thinks the latter is probably going to land us in hot water."
            },
            {
                "id": "82B28AFF-BB29-4186-AA07-04959180D81A",
                "name": "Full English",
                "photoCredit": "Melissa Walker Horn",
                "price": 12,
                "restrictions": ["G"],
                "description": "If you like eating breakfast out of things that could easily be served any time of day, then we have the perfect thing for you. Comes with tomato ketchup or brown sauce depending on how northern you sound."
            },
            {
                "id": "190E1A9D-20A4-4F98-AA54-B192E1C4D7B9",
                "name": "Porridge Deluxe",
                "photoCredit": "Monika Grabkowska",
                "price": 4,
                "restrictions": ["D", "N", "V"],
                "description": "This is literally just porridge. Bob, where did you get this picture? Our porridge looks nothing like this. No porridge does – who the heck has the time to make porridge like this? [NOTE FOR BOB: Replace this with actual marketing text]"
            }
        ]
    }
]

Any help is appreciated. Thanks!

2      

The problem is that you have collapsed two diffferent types from your JSON into a single Swift type.

The outer type in your JSON just has the properties id, name, and items. While the inner type has id, name, photoCredit, price, restrictions, and description.

Just as you made items an Optional since the children do not have that property, likewise you need to make the properties that the parent doesn't have Optional. And then you would have to test in the List body to see if you are dealing with a parent item or a child item and display them accordingly.

2      

@roosterboy, is it possible to implement this expanding list view with a single level of JSON?

How do you suggest that you make one node the parent and another the child?

[
    {
        "parent": "Father",
        "child": "Son"
    }, {
        "parent": "Mother",
        "child": "Daughter"
    }, {
        "parent": "Cow",
        "child": "Calf"
    }
]

2      

@davidzimmerjr
I have been trying to do the very same thing and I found this on Stackoverflow (if you are still looking for an answer)

Try adding this to your List in ContentView: .listStyle(SidebarListStyle())

Here is the original link: https://stackoverflow.com/questions/65110710/what-are-the-requirements-for-a-free-expandable-list-in-swiftui

2      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.