skip to Main Content

I’m pretty new to SWIFT, especially parsing JSON I want to display specific item from array in my friend’s JSON API.

I have this API url: https://api.novella-designer.com/api/:version/tree/preview/307ea989-a7aa-4496-a86e-0577cec10754

My problem is, I can’t display one specific item from that array, here’s my JSON decoding file:

import Foundation

@MainActor

class JsonNovella: ObservableObject {
    
    private struct Returned: Codable {
        var slides: [Slides]
        var status: String
    }
    
    struct Slides: Codable, Hashable {
        var slide: Slide
        
    }
    
    struct Slide: Codable, Hashable {
        var action: [Action]
        var id: String
    }
    
    struct Action: Codable, Hashable{
        var name: String
        var map_action: MapAction
    }
    
    
    struct MapAction: Codable, Hashable{
        var transaction_elements: [TransitionElements]?
    }
    
    struct TransitionElements: Codable, Hashable{
        var component_map: ComponentMap?
    }
    
    struct ComponentMap: Codable, Hashable{
      var elements: [Elements]
    }
    
    struct Elements: Codable, Hashable{
        var color: String
        var name: String
        var image_url: String?
        var type: String
        var text: String?
    }
    
    @Published var urlString = "https://api.novella-designer.com/api/:version/tree/preview/307ea989-a7aa-4496-a86e-0577cec10754"
    @Published var slides: [Slides] = []
    @Published var status = ""
    
    
    
    func getData() async {
        print("We are accessing the url (urlString)")
        
        guard let url = URL(string: urlString) else {
            print("ERROR: could not create URL from (urlString)")
            return
        }
        do  {
            let (data, _) = try await URLSession.shared.data(from: url)
            
            guard  let returned = try? JSONDecoder().decode(Returned.self, from: data) else {
                print("đŸ˜ ERROR: could not decode data  from (urlString)")
                return
            }
            
            self.status = returned.status
            self.slides = returned.slides
            print(returned.slides)
            
        } catch {
            print("ERROR: could not get data  from (urlString)")
        }
    }
}

And here is my SWIFTUI file, I don’t actually know, if im doing right. I’ve watched a lot of videos about parsing JSON, but couldn’t find the solution for me.


import Foundation
import SwiftUI

struct NovellaViewer: View {
    var loop = 5

    @StateObject var novellaJS = JsonNovella()
    var body: some View {
        
        ScrollView() {
            VStack{
                ForEach(novellaJS.slides, id: .self) { slides in

                    
                    ZStack{
                        ForEach(slides.slide.action, id: .self){ slide in
                            ForEach(slide.map_action.transaction_elements ?? [], id: .self) { slide in
                                ForEach(slide.component_map?.elements ?? [], id: .self) { slide in
                                        AsyncImage(url: URL(string: slide.image_url ?? "")) { phase in
                                            if let image = phase.image{
                                                image
                                                    .resizable()
                                                    .scaledToFit()
                                            }
                                        }
                                }
                            }
                        }
                    }
                }
            }
            .task {
              await  novellaJS.getData()
        }
        }
    }
}


#Preview{
    NovellaViewer()
}

I’ve tried to select item from array like using [1], but it doesn’t work for me, I guess, this because it’s JSON, not just array. So I want to have call 1-2 objects from array, just to make character and background behind him.

Can somebody at least give me some info or links. Because im literally spend a week on it.

2

Answers


  1. You need to remove all of the id: .self that is a violation of the ForEach API where the id param must be a unique identifier of the data it cannot be the data itself.

    Best conform Slide to Identifiable, make a var id then it will work with ForEach with no id param required.

    class JsonNovella: ObservableObject needs to be removed or at least not be @MainActor. Since you are using async/await and not Combine it is best to just remove that class and do this:

    .task {
        slides = await novellaJS.getData()
    }
    

    Where slides is @State and novellaJS is a controller in an EnvironmentKey.

    Login or Signup to reply.
  2. ...I can't display one specific item from that array...

    To get a specific item, for example the first image url from your models,
    you need to go through the different model structures dealing with possible Optional
    values (I use if let ...) and assign it to a var (eg: firstUrl).

    The working example code shows how to display the first image you get from the server (red border),
    using func getFirstUrl() or alternatively func getFirstUrl2()

    Note, you should take the advice regarding naming of models, and use Identifiable when needed.

    struct ContentView: View {
        var body: some View {
            NovellaViewer()
        }
    }
    
    @MainActor
    class JsonNovella: ObservableObject {
        
        private struct Returned: Codable {
            var slides: [Slides]
            var status: String
        }
        
        struct Slides: Identifiable, Codable, Hashable {  // <-- here
            let id = UUID()  // <-- here
            var slide: Slide
            
            enum CodingKeys: String, CodingKey {   // <-- here
                case slide
            }
        }
        
        struct Slide: Codable, Hashable {
            var action: [Action]
            var id: String
        }
        
        struct Action: Identifiable, Codable, Hashable {  // <-- here
            let id = UUID()  // <-- here
            var name: String
            var map_action: MapAction
            
            enum CodingKeys: String, CodingKey {   // <-- here
                case name, map_action
            }
        }
        
        struct MapAction: Codable, Hashable{
            var transaction_elements: [TransitionElements]?
        }
        
        struct TransitionElements: Identifiable, Codable, Hashable {  // <-- here
            let id = UUID()  // <-- here
            var component_map: ComponentMap?
            
            enum CodingKeys: String, CodingKey {   // <-- here
                case component_map
            }
        }
        
        struct ComponentMap: Codable, Hashable{
            var elements: [Elements]
        }
        
        struct Elements: Identifiable, Codable, Hashable {  // <-- here
            let id = UUID()  // <-- here
            var color: String
            var name: String
            var image_url: String?
            var type: String
            var text: String?
            
            enum CodingKeys: String, CodingKey {   // <-- here
                case color,name,image_url,type, text
            }
        }
        
        @Published var urlString = "https://api.novella-designer.com/api/:version/tree/preview/307ea989-a7aa-4496-a86e-0577cec10754"
        @Published var slides: [Slides] = []
        @Published var status = ""
        
        
        func getData() async {
            print("---> We are accessing the url (urlString)")
            
            guard let url = URL(string: urlString) else {
                print("ERROR: could not create URL from (urlString)")
                return
            }
            do  {
                let (data, _) = try await URLSession.shared.data(from: url)
                let returned = try JSONDecoder().decode(Returned.self, from: data)  // <-- here
                self.status = returned.status
                self.slides = returned.slides
            } catch {
                print("---> ERROR: (error)") // <-- important
            }
        }
        
    }
    
    struct NovellaViewer: View {
        @StateObject var novellaJS = JsonNovella()
        @State var firstUrl: URL?  // <--- here, for testing
        
        var body: some View {
            VStack {
                
                // to display the first image for testing
                AsyncImage(url: firstUrl) { phase in
                    if let image = phase.image {
                        image.resizable().scaledToFit()
                    }
                }.border(.red, width: 6)
                
                Divider()
                
                ScrollView() {
                    ForEach(novellaJS.slides) { slides in   // <-- here etc...
                        VStack {
                            ForEach(slides.slide.action) { slide in
                                ForEach(slide.map_action.transaction_elements ?? []) { trans in
                                    ForEach(trans.component_map?.elements ?? []) { item in
                                        AsyncImage(url: URL(string: item.image_url ?? "")) { phase in
                                            if let image = phase.image {
                                                image.resizable().scaledToFit()
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            .task {
                await novellaJS.getData()
                getFirstUrl() // <-- here
            }
        }
        
        // the function to retrieve the first `item.image_url` from your models
        func getFirstUrl() {
        outerloop:
            for slides in novellaJS.slides {
                for action in slides.slide.action {
                    if let elements = action.map_action.transaction_elements {
                        for trans in elements {
                            if let compMap = trans.component_map {
                                for item in compMap.elements  {
                                    if let url = item.image_url {
                                        firstUrl = URL(string: url)
                                        break outerloop
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    
    // alternative using index into the various arrays,
    // and dealing with optionals, and index out-of-range prevention
    func getFirstUrl2() {
        if novellaJS.slides.count > 0 {
            let actions = novellaJS.slides[0].slide.action
            if actions.count > 1 {
                if let elements = actions[1].map_action.transaction_elements, elements.count > 0  {
                    if let compMap = elements[0].component_map, compMap.elements.count > 0  {
                        let item = compMap.elements[0]
                        if let url = item.image_url {
                            firstUrl = URL(string: url)
                        }
                    }
                }
            }
        }
    }
        
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search