In this article, I will show you an example of how can you speedup development with code generation scripting.

How can you speed up development with scripted code generation?

For code generation, I usually use Python or Ruby as these languages have a lot of libs for the generation of boilerplate code. And also these languages are first-class citizenship on macOS.

Real-life example: I had a lot of color themes in an application and a lot of colors, my designer changed colors once every month and added a new theme once a year.

Information was provided in JSON format:

{

    "MainTheme": {

        "textPrimary": {

            "light": "#000000",

            "dark": "#FFFFFF"

        },

        "textSecondary": {

            "light": "#8A8A7E",

            "dark": "#8D8D94"

        },
        ...
    },

    ...,

    "BrightTheme": {

        ...

    }

}

I had theme name and color type name with HEX code of color for light and dark modes.

So how should I convert this into the code?

In common situation you can transform hex into UIColor like that:

extension UIColor {

   convenience init(rgb: Int) {
       self.init(
           red: (rgb >> 16) & 0xFF,
           green: (rgb >> 8) & 0xFF,
           blue: rgb & 0xFF
       )
   }
}

let color = UIColor(rgb: 0xFFFFFF)

But it is really uncomfortable to use colors in this way. I prefer color literal because can see the color:

Сolor literal

Yeah, it is much better. Let’s do it this way.

First of all, we should create a swift file where our colors will be — “AppColors.generated.swift”.

After that, as we are using Ruby we should prepare a project for using Ruby.

  • Make sure Ruby and Gem are installed;
  • Call the bundle install command in the project’s iOS root folder — it will install the required gems;
  • Liquid is one of the required gems — we will use it for creating templates.

Also, we should have raw resources for colors — JSON with colors. Let’s name it “ios_colors.json”.

The liquid is very useful for preparing templates. Example of our template “ColorsTemplate.liquid”:

import UIKit

/// Extension representing a pool of new colors

public extension UIColor {

    static func getColor(lightMode: UIColor, darkMode: UIColor) -> UIColor {

        if #available(iOS 13.0, *) {

            return UIColor { (traits) -> UIColor in

                return traits.userInterfaceStyle != .dark ? lightMode : darkMode

            }

        } else {

            return lightMode

        }

    }

{% for keyVal in data %}

    struct {{ keyVal[0] }} {

        {% for key in keyVal[1] %}

            public static let {{ key[0] }}: UIColor = getColor(
                lightMode: #colorLiteral(
                    red: {{ key[1].lightRed }},
                    green: {{ key[1].lightGreen }},
                    blue: {{ key[1].lightBlue }},
                    alpha: {{ key[1].lightOpacity }}
                ),
                darkMode: #colorLiteral(
                    red: {{ key[1].darkRed }},
                    green: {{ key[1].darkGreen }},
                    blue: {{ key[1].darkBlue }},
                    alpha: {{ key[1].darkOpacity }}
                )
            ) ///{{ key[1].lightHex }} | {{ key[1].darkHex }}

            {% endfor %}

     }

{% endfor %}

}

Now the most interesting process — writing script. I will just leave code on Ruby:

require 'Liquid'
require 'json'class Colors
    attr_accessor :lightRed,
                  :lightGreen,
                  :lightBlue,
                  :darkRed,
                  :darkGreen,
                  :darkBlue,
                  :lightHex,
                  :darkHex,
                  :lightOpacity,
                  :darkOpacity
    def initialize(light, dark)
        @lightHex = light
        @darkHex = dark
        if light.size > 7 then
            *tempLight, alpha = light.match(/^#(..)(..)(..)(..)?$/).captures.map(&:hex)
            @lightRed = (tempLight[0] / 255.0).round(2)
            @lightGreen = (tempLight[1] / 255.0).round(2)
            @lightBlue = (tempLight[2] / 255.0).round(2)
            @lightOpacity = (alpha || 255) / 255.0
        else
            tempLight = light.match(/^#(..)(..)(..)$/).captures.map(&:hex)
            @lightRed = (tempLight[0] / 255.0).round(2)
            @lightGreen = (tempLight[1] / 255.0).round(2)
            @lightBlue = (tempLight[2] / 255.0).round(2)
            @lightOpacity = 1.0
        end
        
        if dark.size > 7 then
            *tempDark, alpha = dark.match(/^#(..)(..)(..)(..)?$/).captures.map(&:hex)
            @darkRed = (tempDark[0] / 255.0).round(2)
            @darkGreen = (tempDark[1] / 255.0).round(2)
            @darkBlue = (tempDark[2] / 255.0).round(2)
            @darkOpacity = (alpha || 255) / 255.0
        else
            tempDark = dark.match(/^#(..)(..)(..)$/).captures.map(&:hex)
            @darkRed = (tempDark[0] / 255.0).round(2)
            @darkGreen = (tempDark[1] / 255.0).round(2)
            @darkBlue = (tempDark[2] / 255.0).round(2)
            @darkOpacity = 1.0
        end
    end
    
    def to_hash
        tempHash = Hash.new
        tempHash['lightRed'] = @lightRed
        tempHash['lightGreen'] = @lightGreen
        tempHash['lightBlue'] = @lightBlue
        tempHash['darkRed'] = @darkRed
        tempHash['darkGreen'] = @darkGreen
        tempHash['darkBlue'] = @darkBlue
        tempHash['lightHex'] = @lightHex
        tempHash['darkHex'] = @darkHex
        tempHash['lightOpacity'] = @lightOpacity
        tempHash['darkOpacity'] = @darkOpacity
        tempHash
    end
    def to_json
        to_hash.to_json
    end
endclass Constants
    private
    TABLE_SEPARATOR = "----------------------------------------------------------------------------------"
    JSON_TYPE = ".json"
    COLORS_JSON_FOLDER = 'Utils/Utils/Resources/Colors/'
    TEMPLATE_FILEPATH = 'Utils/Utils/Resources/Colors/ColorsTemplate.liquid'
    COLORS_ENUM_FILEPATH = 'Utils/Utils/Generated/AppColors.generated.swift'
end# Find all colors json files
json_files = Dir.entries(Constants::COLORS_JSON_FOLDER).select {|f| 
    f.include? Constants::JSON_TYPE
}header_string = "Finded JSON files:"
header_string = "|                                #{header_string}                                |"
puts header_string# get all colors from files
color_duplicates = Hash.new
result_json_data = Hash.newjson_files.each do |filename|
    context = File.read("#{Constants::COLORS_JSON_FOLDER}#{filename}")
    @templateFile = File.read(Constants::TEMPLATE_FILEPATH)
    @outputFile = Constants::COLORS_ENUM_FILEPATH
    
    json = JSON.parse(context)
    result_json_data["#{json.keys[0]}"] = Hash[json["#{json.keys[0]}"].map { |key, value|
        [ key, Hash[ Colors.new(value['light'], value['dark']).to_hash ] ]
        }]
end
puts Constants::TABLE_SEPARATOR# convert json feature toggles to enum
@template = Liquid::Template.parse(@templateFile) # Parses and compiles the template
@rendered = @template.render({'data' => result_json_data})
File.write(@outputFile, @rendered)

Ok, usually I save all scripts in the root directory as it is easy to use them. Name of our script file “convertColorsToStatic.rb”.

Let’s call in the terminal:

ruby convertColorsToStatic.rb

Voila:

Code Generation Script for iOS

Also I’m using scripts for creating events, feature toggles, strings, architecture components (for example, VIPER) and it saves me ton of time.

Protocol Buffers vs JSON

Protocol Buffers vs JSON

Protocol Buffers vs JSON

Protocol Buffers vs JSON

Have you ever wondered how your favorite mobile app delivers lightning-fast updates or how social media platforms handle millions of messages per...

A comprehensive guide to mobile app development process

Mobile App Development Process: 7 Steps to Build an App

Mobile App Development Process: 7...

Mobile App Development Process: 7 Steps to Build an App

Developing a mobile application from A to Z is rather long and laborious work. But we have prepared for you a practical application development...

Unicorn Companies' Tech Stacks.

Tech Stack of Prominent Companies: What Are Industry...

Tech Stack of Prominent Companies:...

Tech Stack of Prominent Companies: What Are Industry Giants Using to Power Their Applications?

Netflix vs. Hulu, Hubspot vs. Salesforce, Spotify vs. Pandora, Databrick vs. ByteDance, Canva vs. Miro, and Uber vs Lyft are the rivalries that...