Working With Property Lists in Swift – Part 2

March 5th, 2021

⏱ Reading Time: 7 mins

In the first part of the Working With Property Lists in Swift series, I showcased through a simple example how easy it is to use plist files in order to read and save data. Using the PropertyListEncoder and PropertyListDecoder classes along with the Codable type, encoding and decoding to and from property lists is just a matter of a couple of lines.

However, there is a downside. Codable work instantly out of the box when all properties in a custom type are of basic data types; numbers, string, data, boolean, and a few more (you can find them all in a plist file). Exactly like it happens in the CustomShape sample type that I used for demonstration in the previous part and you can see right next:

What happens when a custom type contains properties of data types incompatible with plists and the Codable type? For example, colors in the CustomShape type are being represented with string values, and pattern image is a Data object. What if we wanted to have UIColor and UIImage objects respectively?

Unfortunately, that’s something that cannot be done directly. UIColor and UIImage are programming entities that cannot be stored natively in property lists, which are XML files. And therefore, they cannot be encoded and decoded using Codable automatically.

But with some additional amount of work we can actually have unsupported types with Codable, and initialize or store them indirectly. And here you are going to meet what takes in order to manage that.

Changing the scenario

At first, let’s make things a bit more interesting, and instead of representing colors as hex with string values in the DefaultShape.plist sample file (presented in the previous part), let’s use arrays of color values. Values on each array match to red, green, blue and alpha respectively, ranging from 0 to 255. The only exception is the alpha value that ranges from 0 to 1:

Updated DefaultShape.plist

Note that the key names have changed as well. The key backgroundColor changed to backgroundColorValues, borderColor changed to borderColorValues, and patternImage changed to patternImageData.

Now let make some modifications to the CustomShape type as well. We are changing both backgroundColor and borderColor property types to UIColor, and patternImage from Data to UIImage. Additionally, we are adding the following three new properties so they match to the updated keys in the DefaultShape.plist file:

What we want to achieve with the above modifications is to decode respective plist values to the new properties shown above, and then use those to initialize the actual color and image objects that will be assigned to the matching properties.

With the above changes Xcode will show an error saying that the CustomShape type does not conform to protocols Encodable and Decodable. That’s most expected, as UIColor and UIImage are types not compatible with Codable.

To work around that and eventually reach our goal we will do the following:

  • We will explicitly specify the properties that conform to Codable, leaving aside those that do not.
  • We’ll become in charge of the decoding process, and we’ll manually populate decoded values to the matching properties.
  • In the end, we’ll create actual color and image objects using the values decoded in the previous step.

Being in control of decoding

In order to explicitly specify the properties that we want to be part of the encoding/decoding process, it’s necessary to define an enumeration that will be conforming to a specific protocol named CodingKey. We have to nest that enumeration in the custom type, and even though it can have any name, we usually call it CodingKeys.

The cases of that enum actually list the properties we want to include in the encoding or decoding, so they must have the exact same name to those properties:

Next, we are going to implement the following init method coming from the Decodable protocol. Normally, it’s called automatically behind the scenes every time data is being decoded. When implemented explicitly though, we can control the exact values that will be decoded. Note that this one is triggered after we call the init(withPlistAt:) custom initializer presented in the previous post:

The first move now is to get the data that will be decoded. We do so by getting a keyed representation of the data, where keys are coming from the CodingKeys enumeration previously specified:

Using the container we can now decode each piece of data, or phrased differently, every single key. Let’s see how to do that for the width value:

In the decode(_:forKey:) method that we access through container, we specify the data type of the currently decoded value, and the matching case in the CodingKeys enum. The decoded result is assigned to the width property.

In an exactly similar fashion we can decode the other properties too:

Note two things here; the first is the data type provided as argument on each method and depends on what should be decoded. The second is to focus on the last two lines, where we are decoding arrays of values and not single values. Notice that in such cases the actual data type is included in brackets; decoder will know that way that what we are trying to decode is an array.

With decoding in place, we can move forward and initialize the color properties:

See that we are proceeding to the colors initialization once we make sure that we have four values on each array (r, g, b, a).

Similarly we can handle the image:

The entire init(from:) method is this:

Color and image objects can now be used normally, even though no UIColor or UIImage exists in the original plist.

Overriding encoding

In a pretty much similar fashion we can override the encoding process and include only properties that can actually be encoded. For that purpose it’s necessary to implement the following initializer:

We begin here by getting a container which will be storing all values to be encoded, keyed by the CodingKeys enum:

Encoding property values now is done like so:

Obviously, we are not going to encode the backgroundColor, borderColor, and patternImage properties, because that’s not possible as explained in the beginning of this post. However, we can include in the encoding process the actual values that form the colors and the data that represents the image (if exists).

Starting with the background color, it’s necessary at first to declare four CGFloat variables where we’ll store the red, green, blue and alpha values respectively:

We can now get color values like so:

We want the values of these variables to be stored into an array in the resulting property list. For that reason, we’ll include them inside brackets while providing them for encoding:

We are going to handle the borderColor exactly as above:

There is obviously no reason to declare new variables to keep the color values; we use the previous ones.

Regarding the image, let’s suppose that we want to get the PNG representation of it, which if exists, is added to the container:

With the steps shown right above, we can now encode the CustomShape instance to a property list, even though there are properties that cannot be actually encoded.

The entire encode(to:) method is presented right next:

If we initialize a CustomShape instance, change a few properties and save it using the save(as:) method that was implemented in the previous post, we will get a new property list file that will be containing all updated values.

Exported Plist

Summary

When a situation is anything else but the perfect one as described in the previous post, then some additional work is what can take us to the desired results. Encoding and decoding custom types that contain properties which cannot be actually included in the process is not impossible. However, more actions and implementation is required from us in order to define a CodingKey-conforming enumeration and override the initializers as presented above; and that’s the place where we should come up with solutions. But in no way the entire process is difficult! Use what you read in this one and the first post as how-to guides, and you will be dealing with property lists at no time!

Stay Up To Date

Subscribe to my newsletter and get notifiied instantly when I post something new on SerialCoder.dev.

    We respect your privacy. Unsubscribe at any time.