Titanium

iOS 13+ Dark Mode in Titanium

Guest poster, Hans Knoechel, was part of the core Titanium team at Axway Appcelerator for 3.5 years. In 2018, he became the founder and CEO of Lambus, a collaborative platform to plan group travel in real time. You can follow him on Twitter at @hansemannnn or on Github at https://github.com/hansemannn.

This blog describes how you can implement full support for iOS 13+ dark mode in Titanium. It works for both semantic colors and images. The project can be found in Github here.

Dark mode

The Magic

This project includes a CLI hook that generates semantic colors and images for the iOS Asset Catalog based on a JSON file of colors that can even be used cross-platform and is backwards compatible. It hooks into the SDK process between generating the asset catalog and compiling the app, so you can even change colors between builds without the need of clean-building the app again.

Requirements

The following project- and OS-requirements are necessary:

  • Xcode 11+
  • Asset Catalog enabled
  • iOS 13+ (will fallback to #000000 if called from older devices)
  • Titanium SDK 8.0.0+
  • A CLI plugin to hook into the asset catalog to generate the semantic colors
  • A JSON file to curate the color styles

Installation

  • Copy the contents of the plugin/ directory (colors) to <project>/plugins
  • Link the colors plugin in your tiapp.xml:
    <ti:app>
     <!-- ... -->
     <plugins>
     <!-- ... -->
     <plugin version="1.0">colors</plugin>
     </plugins>
    </ti:app>
  • Link the native ti.darkmode module to your project like any other native module
  • Alloy: Copy your color JSON file to <project>/app/assets/json/colors.json
  • Classic: Copy your color JSON file to <project>/Resources/json/colors.json
  • For semantic images, make sure they are following the following scheme (-dark suffix):
    # Default (Light)
    image.png
    image@2x.png
    image@3x.png
    
    # Dark
    image-dark.png
    image-dark@2x.png
    image-dark@3x.png
  • Map the colors on runtime for older devices or Android (this is just an example of how this could look like):
    function initializeColors() {
      const colors = Alloy.Globals.colors = JSON.parse(Ti.Filesystem.getFile('json/colors.json').read());
      const darkmode = OS_ANDROID ? undefined : require('ti.darkmode');
    
      for (const color of Object.keys(colors)) {
        Alloy.CFG.styles[color] = Utils.isiOSVersionOrGreater(13) ? darkmode.fetch(color) : colors[color].light;
      }
    
      // Use your colors like the following
      myLabel.backgroundColor = Alloy.CFG.styles.backgroundColor
    }

To dos

  • This may break incremental builds, since we generate files to the build dir that are not tracked by the CLI so far
  • Make all color-setters ready to receive a TiColor instance (see this pull request for details)