1
votes

I tried to create an ObservableObject with non array @Published item. However, I still don't know how to do so. I tried to use a ? to do so. But when I display it in view like Text((details.info?.name)!), and it return Thread 1: Swift runtime failure: force unwrapped a nil value I don't know what the problem and how to solve. Is it my method of creating observable object class are correct?

class ShopDetailJSON: ObservableObject {
    
    @Published var info : Info?

    init(){load()}
    
    func load() {

        URLSession.shared.dataTask(with: request) { data, response, error in
            guard let data = data else {
                print("No data in response: \(error?.localizedDescription ?? "Unknown error").")
                return
            }
            if let decodedShopDetails = try? JSONDecoder().decode(ShopDetail.self, from: data) {
                DispatchQueue.main.async {
                    self.info = decodedShopDetails.info!
                }
            } else {
                print("Invalid response from server")
            }
        }.resume()
    }
    
}
struct Info : Codable, Identifiable {
    let contact : String?
    let name : String?
    var id = UUID()

    enum CodingKeys: String, CodingKey {

        case contact = "contact"
        case name = "name"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        contact = try values.decodeIfPresent(String.self, forKey: .contact)
        name = try values.decodeIfPresent(String.self, forKey: .name)
    }

}
struct ShopDetail : Codable {
    let gallery : [Gallery]?
    let info : Info?

    enum CodingKeys: String, CodingKey {

        case gallery = "gallery"
        case info = "info"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        gallery = try values.decodeIfPresent([Gallery].self, forKey: .gallery)
        info = try values.decodeIfPresent(Info.self, forKey: .info)
    }

}

Sample JSON data

{

    "gallery": [],
    "info": {
        "contact": "6012345678",
        "name": "My Salon",
    }
}
1
You don't need to implement .init(from:) or have a custom CodingKey - just conforming to Decodable/Encodable (or Codable) is enough, i.e. struct ShopDetail: Codable { var gallery: [Gallery]?; var info: Info? }... other than that, you're trying to use ! on a nil value you get this error. Why this value is nil is hard to say, since we don't know what the JSON that you're decoding looks like - New Dev
@NewDev edited with sample json - Oxford212
The data won't be nil, at least will be a empty string "" - Oxford212
If the sample JSON is correct, then you don't need to make ShopDetail.Info optional, and ShopDetail.gallery can just be [Gallery] - not an optional array. Also, avoid using ! to force unwrap. It's hard to be sure what the error, since you're not showing where the error actually occurs. My guess is that you're trying to access ShopDetailJSON.info before it's been loaded, which fails. If so, put a conditional: if let info = details.info { Text(info.name) } - New Dev

1 Answers

0
votes

This is answer is a bit of a guess as to what happens in your code, but if the JSON data is never null, as you say in the comments, it's likely that you're trying to access a not-yet-updated ShopDetailJSON.info optional property in your view.

First, some clean-up. You don't need to the custom implementation of init(from:) - just conforming to Codable is enough in your case. And if the JSON values aren't optional, no need to make them into an optional type:

struct Info: Codable, Identifiable {
    let contact : String
    let name : String
    var id = UUID()
}

struct ShopDetail: Codable {
    let gallery : [Gallery]
    let info : Info
}

Then, when you get the JSON you wouldn't need to deal with optionals and force-unwrap ! (which should have been avoided anyways):

if let decodedShopDetails = try? JSONDecoder().decode(ShopDetail.self, from: data {
    DispatchQueue.main.async {
        self.info = decodedShopDetails.info // no force-unwrap needed
    }
}

In the view, you need to check that the info property is not nil before accessing its elements.

struct ContentView: View {

   @ObservedObject var details: ShopDetailJSON

   var body: some View {
       Group() {

           // on iOS14
           if let info = details.info {
               Text(info.name)
           }

           // pre iOS14
           // if details.info != nil {
           //    Text(details.info!.name)
           // }
       }
       .onAppear {
           self.details.load()
       }
   }
}