Using JSON files to configure app styles
In this post I am going to write about how you can use JSON file to style your app. As a bonus point we will also see how different JSON files can be used to style different targets of the same app.
Styling is hard, especially when you have different screens or targets. Usually in the app you encode information in plist
file or add ifdef
s. Based on the current target we will then apply appropriate styles. This is not exactly feasible - Especially when you have multiple files and/or targets and code gets bloated with numerous if-else
statements.
In this post, I will demonstrate the approach I have been using in my side projects. This is not novel or original in the sense, but I really like it and putting things in local JSON also implies that data can be fed and directed from server as well.
Let's start! Say you have a project named CustomStyles
. Hypothetically you have 3 targets and each of them needs a custom style on its own. (Targets are CustomStyles
, CustomStyles1
and CustomStyles2
)
Now, we can either do if-else
in the project or make a JSON files for individual projects. These JSON file will be read and an object of type AppStyle
will be created which will dictate the styles to use.
To start with let's look at JSON file of one of the targets,
{
"styles": {
"background-color": "#d8dcff",
"text-color": "#53e6da",
"font-name": "AmericanTypewriter",
"font-size": 16,
"corner-radius": 30
},
"image": {
"main": "horse",
"chevron": "chevron"
},
"animation": true,
"text": "App 1"
}
We will use the similar json file but with different values for other targets as well. I will use the same json file name for all 3 targets, but every file will be in its own target
Xcode will not allow you to create files with same name under same folder. Solution is to create 3 json files with same name, say
app1.json
and store them under different folders on file system. Once this is done you can drag and drop individual file in the project. Here's how it will look like
Their target membership is described below,
JSON/app1/app1.json - CustomStyles
JSON/app2/app1.json - CustomStyles1
JSON/app3/app1.json - CustomStyles2
As it is cleared from JSON, we have use following styles which are specific to targets,
- background-color
- text-color
- font-name
- font-size
- corner-radius
- main-image
- chevron-image
- animation
- text
Creating AppStyle
object
First off we read a JSON from local file structure. (For the practical purpose, this could well be coming back from server).
class JSONFileReader: NSObject {
static func readJSONFile(with name: String) -> AppStyle? {
guard let path = Bundle.main.path(forResource: name, ofType: "json") else { return nil }
do {
let jsonData = try NSData(contentsOfFile: path, options: NSData.ReadingOptions.mappedIfSafe)
do {
let jsonResult: NSDictionary = try JSONSerialization.jsonObject(with: jsonData as Data, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
if let style = jsonResult as? NSDictionary {
return AppStyle(styleInformation: style)
}
} catch {
print("Error occurred while trying to convert JSON data into Swift types")
}
} catch {
print("Error occurred while trying to read contents of file \(name)")
}
/// Simply return a nil value when unpleasant situation is encountered.
return nil
}
}
Please note how we use only one JSON file name
app1
for all 3 targets. This is because each of the three JSON file is specific to checked target. There are no two JSON files with same name in the same target. So depending on the target for which you are running project, appropriate JSON file will be picked up.
Once we successfully read from the file, a Dictionary
object will be passed to AppStyle
initializer which will return the initialized AppStyle
object back.
class AppStyle {
let animation: Bool
var mainImage: UIImage?
let chevronImage: UIImage
var backgroundColor: UIColor?
var textColor: UIColor?
let cornerRadius: CGFloat
let font: UIFont
let text: String
init(styleInformation: NSDictionary) {
animation = (styleInformation["animation"] as? Bool) ?? false
chevronImage = UIImage(named: Image.chevron.rawValue)!
text = (styleInformation["text"] as? String) ?? ""
if let imageInfo = styleInformation["image"] as? NSDictionary, let mainImageInfo = imageInfo["main"] as? String, let actualImage = UIImage(named: mainImageInfo) {
mainImage = actualImage
}
if let styles = styleInformation["styles"] as? NSDictionary {
backgroundColor = (styles["background-color"] as? String)?.hexStringToUIColor()
textColor = (styles["text-color"] as? String)?.hexStringToUIColor()
cornerRadius = (styles["corner-radius"] as? CGFloat) ?? 0.0
if let fontName = styles["font-name"] as? String, let fontSize = styles["font-size"] as? Int, let assignedFont = UIFont(name: fontName, size: CGFloat(fontSize)) {
font = assignedFont
} else {
font = UIFont.systemFont(ofSize: 14)
}
} else {
cornerRadius = 0.0
font = UIFont.systemFont(ofSize: 14)
}
}
}
// Initialize AppStyle object
let currentStyle = JSONFileReader.readJSONFile(with: "app1")
Once AppStyle
object is ready, we can use it directly in our ViewController
class. Below are some of the UI elements in this UIKit
class which we will decorate using AppStyle
object
let fullView = UIView()
let imageView = UIImageView()
let chevronImageView = UIImageView()
let textLabel = UILabel()
Obviously, we don't want to show them in vanilla appearance. So we will use our AppStyle
object and use its properties to style these elements.
I have also sprinkled a code with comments for easier understanding
// Read the JSON file named app1.json. Every target gets its own app1.json. Proceed only if we could get valid AppStyle instance
if let currentStyle = JSONFileReader.readJSONFile(with: "app1") {
// Below is the demonstration of how we use different AppStyle properties to style the on-screen elements
fullView.layer.cornerRadius = currentStyle.cornerRadius
fullView.backgroundColor = currentStyle.backgroundColor
imageView.image = currentStyle.mainImage
chevronImageView.image = currentStyle.chevronImage
textLabel.textColor = currentStyle.textColor
textLabel.text = currentStyle.text
textLabel.font = currentStyle.font
// Animation property dictates if image on the view port appears with an animation or not. Default duration is 2 seconds. You can also make it to fetch from app styles JSON document
if currentStyle.animation {
UIView.animate(withDuration: 2.0, animations: {
self.imageView.alpha = 1.0
})
} else {
imageView.alpha = 1.0
}
}
For the sake of saving space, I have decided not to add style JSONs for all 3 app targets. However, I am adding the screenshots of how screens for those targets would look like
Target 1
Target 2
Target 3
Please note how all these screens look similar in layouts, but only differ in styles including color and fonts. Also, having JSON in app may not be feasible all the time, if requirements imply you may even download the JSON directly from server based on the target for which app is currently run.
A source code of this project is included in the Github repository. Please let me know if you have any more thoughts about this approach