Search code examples
iosswiftcontentful

How to access image from Contentful in Swift?


This is how I fetch my entries:

func fetchAllEntriesByLocation(latitude:Double, longitude: Double, completion: @escaping (([Item]) ->Void)) {
    var items:[Item] = []

    let query = Query(where: "content_type", .equals("item")).where("fields.location", .isNear(Location(latitude: latitude, longitude: longitude)))
    client.fetchEntries(with: query) { [weak self] result in
        guard let strongSelf = self else { return }
        DispatchQueue.main.async {
            strongSelf.delegate?.finishedTask()
        }
        switch result {
        case .success(let entries):
            entries.items.forEach { entry in
                items.append(Item(entry: entry))
            }
            DispatchQueue.main.async {
                completion(items)
            }
        case .error(let error):
            print(error)
        }
    }

}

and how I map them

final class Item: EntryModellable {

static let contentTypeId: String = "item"

let name: String
let location: Location
let address: String
let open: String
let desc: String
let phone: String
let url: String
let thumbUrl: Asset


init(entry: Entry) {
    self.name = entry.fields["name"] as? String ?? ""
    self.location = entry.fields["location"] as? Location ?? Location(latitude: 52.23, longitude: 20.9)
    self.open = entry.fields["open"] as? String ?? ""
    self.address = entry.fields["address"] as? String ?? ""
    self.desc = entry.fields["opis"] as? String ?? ""
    self.phone = entry.fields["phone"] as? String ?? ""
    self.url = entry.fields["www"] as? String ?? ""
    self.thumbUrl = entry.fields["thumb"] as? Asset
}

}

The problem occurs when I try to map to Asset type. Although when I print my entries I see that for "thumb" key, the type of the variable is Contentful.Link.asset(Contentful.Asset) . Therefore I cannot cast it to Asset type.

All I want to achieve is to store image Url to use it later with Kingfisher or a similar library to load the image.

What is the correct way of storing an image to access its url?

EDIT:

I tried the solution presented below and now I get the following runtime error: enter image description here

The error is:

Fatal error: Unexpectedly found nil while unwrapping an Optional value


Solution

  • It looks like you are using an older version of the SDK. The contentful.swift SDK is now at version 1.0.1 and it's highly recommended to upgrade to the newest version which has brought API stability, and different interface for mapping data to your own types using the EntryDecodable protocol. It leverages Swift 4's Decodable protocol so you are in control of how JSON maps to your types using standard lib + some helper extension methods provided by the SDK!

    With contentful.swift 1.0.1, the EntryDecodable protocol will properly link all of your types and assets without creating duplicates. Your class would look like the following (note that if you have properties that are not required in Contentful (and therefore could be omitted in a response) you should use decodeIfPresent which returns an optional.

    final class Item: EntryDecodable, ResourceQueryable {
    
        static let contentTypeId: String = "item"
    
        let sys: Sys
        let name: String
        let location: Location
        let address: String
        let open: String
        let desc: String
        let phone: String
        let url: String
    
        // Links must be optional variables as they are resolved after the initializer exits        
        var thumbUrl: Asset? 
    
        public required init(from decoder: Decoder) throws {
            sys             = try decoder.sys()
            let fields      = try decoder.contentfulFieldsContainer(keyedBy: Item.Fields.self)
            name            = try fields.decode(String.self, forKey: .name)
            desc            = try fields.decode(String.self, forKey: .desc)
            location = try? fields.decodeIfPresent(Location.self, forKey: .location) ?? Location(latitude: 52.23, longitude: 20.9)
            address = try fields.decode(String.self, forKey: .address)
            open = try fields.decode(String.self, forKey: .open)
            phone  try fields.decode(String.self, forKey: .phone)
            url = try fields.decode(String.self, forKey: .url) 
    
    
            // Here is how you resolve your Asset.
            try fields.resolveLink(forKey: .thumbUrl, decoder: decoder) { [weak self] linkedAsset in
                self?.thumbUrl = linkedAsset as? Asset
            }
        }
    
        // If your Contentful field names differ from your property names, define the mapping here.
        enum Fields: String, CodingKey {
            case name, location, open, address, phone
            case url      = "www"
            case desc     = "opis"
            case thumbUrl = "thumb"
        }
    }
    

    Now, you'll need to update your query:

    let query = QueryOn<Item>.where(field: .location, .isNear(Location(latitude: latitude, longitude: longitude)))
    client.fetchMappedEntries(matching: query) { [weak self] (result: Result<MappedArrayResponse<Item>>) in
        // Handle your data here, the thumbUrl field will contain a resolved asset for your entries (if that relationship exists in Contentful)
        switch result {
            case .success(let arrayResponse):
                if let firstItem = arrayResponse.items.first {
                    print(firstItem.thumburl?.urlString) 
                }
            case .error(let error):
                print(error)        
        }
    }