NEW: Start my new Ultimate Portfolio App course with a free Hacking with Swift+ trial! >>

SOLVED: Trying to make a Observable Object with an array of Codable objects to be able to reference it anywhere in my app.

Forums > SwiftUI

Hi there!

I'm new to swift but have experience coding in other languages, mostly java.

I've looked through many of the different tutorials available at this site, and even though I'm starting to get used to some of the quirks of swift and swiftui there are still some things I'm having trouble to wrap my head around, like how to use the properties correctly for example.

Here's what I'm struggling with currently and would like some help with: I have made a MainView that downloads a JSON from a website (redacted here since I don't want it publicly available) and decode it through the use of a Codable class object and use parts of it to populate a list, so far so good. I'm now trying to make that list of objects available to other views and classes using ObservableObjects but haven't managed so far. The simplest solution seemed to be (at least to me) making a separate ObservableObject class that just contains an array of my Codable objects, but I haven't been able to make it work so far.

This data will later be stored in a database, but I've tried to just focus on the GUI first and will focus on building the database and all it's interactions later on, since I figured I should try to understand how this property business works. But maybe building up the database and all it's interactions would solve this issue?

Here's my working code:

"Main View"


import SwiftUI

struct Response: Codable {
    var configurations: [ConfigurationVO]
}

struct MainView: View {
    @State private var configurations = [ConfigurationVO]()

    var body: some View {
        List(configurations, id: \.uid) { item in
            VStack(alignment: .leading) {
                Text(item.name)
                    .font(.headline)
            }

        }
        .onAppear(perform: loadData)
    }

    func loadData(){
        guard let url = URL(string:"https://www.************com/*********************") else {
            print("Invalid URL")
            return
        }

        let request = URLRequest(url:url)

        URLSession.shared.dataTask(with: request) { data, response, error in
            if let data = data {
                if let data = Data(base64Encoded: data){
                    if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
                        // we have good data – go back to the main thread
                        DispatchQueue.main.async {
                            // update our UI
                            self.configurations = decodedResponse.configurations
                        }

                        // everything is good, so we can exit
                        return
                    }
                }
            }

            // if we're still here it means there was a problem
            print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
        }.resume()
    }
}

struct MainView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            MainView()
        }
    }
}

"ConfigurationVO"

import Foundation

class ConfigurationVO: ObservableObject, Codable {
    enum CodingKeys: CodingKey {
        case uid,fkTypeId,name
    }
    @Published var uid: String = ""
    @Published var fkTypeId:Int = -1
    @Published var name: String = ""

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        uid = try container.decode(String.self, forKey: .uid)
        fkTypeId = try container.decode(Int.self, forKey: .fkTypeId)
        name = try container.decode(String.self, forKey: .name)
    }

    init(){

    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(uid, forKey: .uid)
        try container.encode(fkTypeId, forKey: .fkTypeId)
        try container.encode(name, forKey: .name)
    }

}

Then I tried making a new class "ConfigurationList" import Foundation

class ConfigurationList:ObservableObject{
    @Published var configurations = [ConfigurationVO]()
}

and changed

@State private var configurations = [ConfigurationVO]()

in MainView to

@ObservedObject var configurations= ConfigurationList()

but that created the following issues that I haven't been able to solve yet:

  • "Initializer 'init(_:id:rowContent:)' requires that 'ConfigurationList' conform to 'RandomAccessCollection'" (while creating the List)
  • "Cannot assign to property: 'self' is immutable" (while putting the decodedResponse into configurations)

I tried to solve it using the $-operator, but that's another quirk I haven't fully grasped yet.

Am I on the right track or totally of the rails? If so how would I go about doing this?

   

hi,

the syntax may be confusing you because of your name choices ... you have a variable configurations that is an object of type ConfigurationList, and this object has a property named configurations of type [ConfigurationVO].

so to refer to the list of ConfigurationVO objects, you have to write configurations.configurations.

perhaps you should be writing

struct MainView: View {
    @StateObject private var configurationList = ConfigurationList() // use @StateObject & rename the variable

    var body: some View {
        List(configurationList.configurations, id: \.uid) { item in // notice reference to list of objects
          // everything else as before

}

and when updating the UI after receiving a valid response you should write

    self.configurationList.configurations = decodedResponse.configurations

hope that helps,

DMG

   

Hi delawaremathguy,

Thank you for the help, you were correct! When you pointed that out I realized my mistake referencing the list of objects instead of the objects themselves. Such a simple mistake and I didn't even see it because I was so focused on understanding the properties...

But why are you recommending @StateObject instead of @ObservedObject? I understand that it has to do with ownership of the object, but I'm not sure exactly how that works. I want to be able to refence this list from at least two other views, and since I wanted the list to live regardless of which view was active I thought it was better that none of them "owned" it. But what happens when there are no @StateObjects or if I have multiple @StateObject?

   

hi,

But why are you recommending @StateObject instead of @ObservedObject?

i'll refer you to this article by Paul Hudson about this iOS 14 property wrapper.

I want to be able to refence this list from at least two other views

i suspect you'll now want to place this object of type ConfigurationList into the environment of all views, and in those other views, refer to this object using @EnvironmentObject. Paul has an article on this as well.

hope that helps,

DMG

   

Hacking with Swift is sponsored by Emerge

SPONSORED Emerge helps iOS devs write better, smaller apps by profiling binary size on each pull request and surfacing insights and suggestions. Companies using Emerge have reduced the size of their apps by up to 50% in just the first day. Built by a team with years of experience reducing app size at Airbnb.

Set up a demo!

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

Reply to this topic…

You need to create an account or log in to reply.

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.