July 17, 2019

Easily Conform to Codable

Learn how to fix Type does not conform to protocol 'Decodable'

Easily Conform to Codable

Today I will show you how to fix Type does not conform to protocol 'Decodable'. The fix is not difficult but it can be annoying that one needs to write wrappers for the types that do not conform.

Lets get started. The below code is my broken example.

struct User: Codable {
    let name: String
    let email: String
    let profilePicture: UIImage
    let location: CLLocationCoordinate2D
}

The problem with the above code is that both UIImage and CLLocationCoordinate2D do not conform to Decodable. The fix is straight forward but it is significantly more code than the default struct from above.

Before we fix the above issue, one needs to take a look at the type that you are trying to create a wrapper for. In this case we are trying to make UIImage codable as well as CLLocationCoordinate2D.

For UIImage we can use the Data type in Swift. Data is codable therefore we can use it in our wrapper. With CLLocationCoordinate2D the latitude and longitude is of type Double, Double is also a codable type.

So when you are going to write a wrapper try and find a codable representation of whatever type is causing the issue. UIColor for example uses CGFloat to represent the red, green, blue and alpha channels. CGFloat is codeable so one would need to use that.

Ok, lets start fixing this problem.

First we will fix the profilePicture property. To that we need to create a new struct. Like I said we need to represent an image as Data.

struct Image: Codable{
    let imageData: Data?
    
    init(withImage image: UIImage) {
        self.imageData = image.pngData()
    }

    func getImage() -> UIImage? {
        guard let imageData = self.imageData else {
            return nil
        }
        let image = UIImage(data: imageData)
        
        return image
    }
}

The Image struct is now codable. To do this I created an imageData property of type Data. This is required as we can only use a codable type for the property otherwise we will be stuck in the same situation we had before.

The next thing I done was create a custom initializer. This allows us to initialize the struct with a UIImage. We then use that UIImage and call the pngData() method. The pngData()method has a return type of Data?, this works perfectly for our situation.

Now the issue is that we want to get a UIImage from this struct, we don’t always want to convert it ourselves. For this I created a simple get method that will return an optional UIImage. This is required as the argument type is Data, Data could mean anything, it could even be text, so initializing a UIImage with data return an optional UIImage.

Now we will either return nil or the image.

We can now change the profilePicture property to be of type Image, as below.

struct User: Codable {
    let name: String
    let email: String
    let profilePicture: Image
    let location: CLLocationCoordinate2D
}

Awesome, now that that one is sorted lets move onto the location property.

We need to create a new Coordinate type. This will have two properties, latitude and longitude and they will be of type Double. This works for us as Double is codable.

In the same way we created a getter in the Image type, we have a similar method that will create and return a new CLLocationCoordinate2D based on the two properties.

struct Coordinate: Codable {
    let latitude: Double
    let longitude: Double

    func locationCoordinate() -> CLLocationCoordinate2D {
        return CLLocationCoordinate2D(latitude: self.latitude,
                                      longitude: self.longitude)
    }
}

And it is as simple as that. We now have a codable Coordinate.

We can update the location property in the User type now to look like the below.

struct User: Codable {
    let name: String
    let email: String
    let profilePicture: Image
    let location: Coordinate
}

Conclusion

That’s it, the User type is now codable and there are no more errors. I hope this has helped.