1

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",
    }
}
Oxford212
  • 89
  • 1
  • 8
  • 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 Oct 10 '20 at 07:03
  • @NewDev edited with sample json – Oxford212 Oct 10 '20 at 07:23
  • The data won't be nil, at least will be a empty string "" – Oxford212 Oct 10 '20 at 08:24
  • 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 Oct 10 '20 at 15:10

1 Answers1

0

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()
       }
   }
}
New Dev
  • 48,427
  • 12
  • 87
  • 129