Working With Property Lists in Swift — Part 2

struct CustomShape {
var width: CGFloat = 0
var height: CGFloat = 0
var backgroundColor: String = "000000"
var borderColor: String = "000000"
var borderWidth: CGFloat = 0
var cornerRadius: CGFloat = 0
var isInteractive: Bool = false
var patternImage: Data = Data()
}

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:

struct CustomShape: Codable {
var backgroundColor: UIColor = .clear
var borderColor: UIColor = .clear
var patternImage: UIImage?

// New properties:
var backgroundColorValues = [CGFloat]()
var borderColorValues = [CGFloat]()
var patternImageData = Data()

...
}
  • 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.

enum CodingKeys: CodingKey {
case width, height, borderWidth, cornerRadius, isInteractive, backgroundColorValues, borderColorValues, patternImageData
}
init(from decoder: Decoder) throws {

}
let container = try decoder.container(keyedBy: CodingKeys.self)
width = try container.decode(CGFloat.self, forKey: .width)
height = try container.decode(CGFloat.self, forKey: .height)borderWidth = try container.decode(CGFloat.self, forKey: .borderWidth)cornerRadius = try container.decode(CGFloat.self, forKey: .cornerRadius)isInteractive = try container.decode(Bool.self, forKey: .isInteractive)patternImageData = try container.decode(Data.self, forKey: .patternImageData)backgroundColorValues = try container.decode([CGFloat].self, forKey: .backgroundColorValues)borderColorValues = try container.decode([CGFloat].self, forKey: .borderColorValues)
if backgroundColorValues.count == 4, borderColorValues.count == 4 {
backgroundColor = UIColor(red: backgroundColorValues[0]/255,
green: backgroundColorValues[1]/255,
blue: backgroundColorValues[2]/255,
alpha: backgroundColorValues[3])

borderColor = UIColor(red: borderColorValues[0]/255,
green: borderColorValues[1]/255,
blue: borderColorValues[2]/255,
alpha: borderColorValues[3])
}
if patternImageData.count > 0 {
patternImage = UIImage(data: patternImageData)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

width = try container.decode(CGFloat.self, forKey: .width)

height = try container.decode(CGFloat.self, forKey: .height)

borderWidth = try container.decode(CGFloat.self, forKey: .borderWidth)

cornerRadius = try container.decode(CGFloat.self, forKey: .cornerRadius)

isInteractive = try container.decode(Bool.self, forKey: .isInteractive)

patternImageData = try container.decode(Data.self, forKey: .patternImageData)

backgroundColorValues = try container.decode([CGFloat].self, forKey: .backgroundColorValues)

borderColorValues = try container.decode([CGFloat].self, forKey: .borderColorValues)

if backgroundColorValues.count == 4,
borderColorValues.count == 4 {
backgroundColor = UIColor(
red: backgroundColorValues[0]/255,
green: backgroundColorValues[1]/255,
blue: backgroundColorValues[2]/255,
alpha: backgroundColorValues[3])

borderColor = UIColor(
red: borderColorValues[0]/255,
green: borderColorValues[1]/255,
blue: borderColorValues[2]/255,
alpha: borderColorValues[3])
}


if patternImageData.count > 0 {
patternImage = UIImage(data: patternImageData)
}
}
borderColorValues = try container.decode([CGFloat].self, forKey: .borderColorValues)

if backgroundColorValues.count == 4, borderColorValues.count == 4 {
backgroundColor = UIColor(red: backgroundColorValues[0]/255,
green: backgroundColorValues[1]/255,
blue: backgroundColorValues[2]/255,
alpha: backgroundColorValues[3])

borderColor = UIColor(red: borderColorValues[0]/255,
green: borderColorValues[1]/255,
blue: borderColorValues[2]/255,
alpha: borderColorValues[3])
}


if patternImageData.count > 0 {
patternImage = UIImage(data: patternImageData)
}
}
func encode(to encoder: Encoder) throws {

}
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(width, forKey: .width)
try container.encode(height, forKey: .height)
try container.encode(borderWidth, forKey: .borderWidth)
try container.encode(cornerRadius, forKey: .cornerRadius)
try container.encode(isInteractive, forKey: .isInteractive)
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
backgroundColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
try container.encode([red * 255, green * 255, blue * 255, alpha], forKey: .backgroundColorValues)
borderColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)try container.encode([red * 255, green * 255, blue * 255, alpha], forKey: .borderColorValues)
if let data = patternImage?.pngData() {
try container.encode(data, forKey: .patternImageData)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)

try container.encode(width, forKey: .width)
try container.encode(height, forKey: .height)
try container.encode(borderWidth, forKey: .borderWidth)
try container.encode(cornerRadius, forKey: .cornerRadius)
try container.encode(isInteractive, forKey: .isInteractive)

var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0

backgroundColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
try container.encode([red * 255, green * 255, blue * 255, alpha], forKey: .backgroundColorValues)

borderColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
try container.encode([red * 255, green * 255, blue * 255, alpha], forKey: .borderColorValues)

if let data = patternImage?.pngData() {
try container.encode(data, forKey: .patternImageData)
}
}
var shape = CustomShape(withPlistAt: defaultShapeURL)

shape.width = 500
shape.height = 250
shape.backgroundColor = .blue
shape.borderColor = .yellow
shape.borderWidth = 5
shape.cornerRadius = 15

shape.save(as: "myShape")

An iOS & macOS app maker writing code in Swift. Author of countless programming tutorials. Content creator. https://serialcoder.dev

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store