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:
The error is:
Fatal error: Unexpectedly found nil while unwrapping an Optional value
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)
}
}