Readium Logo

Preferences API

Summary

The Preferences API is a tool for creating components configurable during runtime. It provides a useful framework for constructing user settings interfaces, taking into account all the rules and dependencies associated with each setting. This makes it easy for developers to create a user interface that can be customized to their individual needs.

Multiple Readium components, such as Navigators, use this new framework to provide a unified API that enables them to change their settings on-the-fly.

Motivation

Navigators in Readium are built differently depending on the type of publication. In the past, the API for modifying their presentation varied across implementations, making it more difficult to build a user interface and store user preferences.

Developers now have access to a single API enabling them to reuse user interface views across types of publications, such as:

Developer Guide

Overview

The Navigator and other Readium components support dynamic configuration through the Configurable interface. This makes it simple to create a settings interface for users and save their preferences as a JSON object.

The application can’t directly set the Navigator settings. Instead, it can submit a Preferences set to the Navigator (Configurable), which will recalculate its actual settings and refresh the display. The application can then update its user settings interface with the new settings emitted by the Navigator.

// 1. Create a set of preferences.
let preferences = EPUBPreferences(
    fontFamily: .serif,
    fontSize: 2.0,
    publisherStyles: false
)

// 2. Submit the preferences, the Navigator will update its settings and the presentation.
epubNavigator.submitPreferences(preferences)

// 3. Read the new settings.
assert(epubNavigator.settings.fontFamily == .serif)

Editing preferences

Configurable objects usually provide a PreferencesEditor to help you create or modify preferences. They offer a collection of adjustable Preference<Value> properties, with rules and helpers to modify their values depending on their type.

// 1. Create a preferences editor.
let editor = EPUBPreferencesEditor(preferences)
    
// 2. Modify the preferences through the editor.
editor.fontFamily.set(.serif)
editor.fontSize.increment()
editor.publisherStyles.toggle()

// 3. Submit the edited preferences.
epubNavigator.submitPreferences(editor.preferences)

Setting the initial Navigator preferences and app defaults

When opening a publication, you can immediatly apply user preferences by passing them to the Navigator constructor. The API for this varies depending on the Navigator implementation, but usually looks like this:

let navigator = try EPUBNavigatorViewController(
    publication: publication,
    config: .init(
        preferences: EPUBPreferences(
            language: Language(code: "fr")
        ),
        defaults: EPUBDefaults(
            pageMargins: 1.5,
            scroll: true
        )
    )
)

The defaults are used as fallback values when the default Navigator settings are not suitable for your application.

Inactive settings

If the activation conditions of a setting are not met in the preferences, the setting is inactive. The Navigator will not consider inactive settings when updating its presentation. For example, the EPUB word spacing setting will only take effect if the publisher styles are disabled.

You can use the PreferencesEditor to determine if a setting is effective for a given set of preferences.

let editor = EPUBPreferencesEditor(preferences)
editor.wordSpacing.isEffective

Preferences are low-level

Preferences are low-level technical settings. Some of them can be shown to the user, like the font size, but others should not be displayed as-is.

For example, we can display two EPUB pages side-by-side using the columnCount (auto, 1, 2) property for reflowable resources and the spread (auto, never, always) property for fixed-layout publications. Instead of displaying both of these settings with all their possible values in the user interface, it may be more user-friendly to show a single switch button to activate dual-page mode, which will set both settings correctly.

Saving and restoring user preferences

All Preferences types can be serialized to and from JSON, which is useful for saving and restoring the selected preferences for future sessions. The API differs depending on the platform.

let jsonData = try JSONEncoder().encode(preferences)

When you are ready to restore the user preferences, construct a new Preferences object from the JSON data.

let preferences = try JSONDecoder().decode(EPUBPreferences.self, from: jsonData)

Splitting and merging preferences

The way you store user preferences can impact the features available. For instance, you might have:

The toolkit provides suggested filters for each Preferences type to help you extract the preferences that are intrinsic to a publication.

let publicationPrefs = preferences.filterPublicationPreferences()
let sharedPrefs = preferences.filterSharedPreferences()

// You can reconstruct the original preferences by combining the filtered ones.
let combinedPrefs = publicationPrefs.merging(sharedPrefs)

:warning: Preferences that are specific to a certain publication, such as language, should not be shared across different publications. It is recommended to store these preferences separately for each book. Using the suggested filters will accomplish this.

Reference Guide

:point_up: This reference is only a broad guide. Each platform should use the most idiomatic patterns to implement this API.

Configurable<S : Configurable.Settings, P : Configurable.Preferences> Interface

A Configurable is a component with a set of Configurable.Settings.

Properties

Methods

Configurable.Settings Interface

Marker interface for the setting properties holder.

Configurable.Preferences Interface

Marker interface for the preferences properties holder.

Each implementation must adhere to the interface that enables JSON serialization and deserialization on the platform.

Methods

PreferencesEditor<P : Configurable.Preferences> Interface

Interactive editor of preferences. This can be used as a helper for a user preferences screen.

Each implementation provides a set of Preference<V> properties to modify the associated preference value.

Properties

Methods

Preference<V> Interface

A handle to edit the value of a specific preference which is able to predict which value the Configurable will effectively use.

Properties

Methods

Preference<Boolean>

A Preference with a boolean for value.

Methods

EnumPreference<V>

A Preference which accepts a closed set of values.

Properties

RangePreference<V>

A Preference whose values must lie in a range of V.

Properties
Methods

Mapping helpers

The toolkit ships with mapping helpers to create new custom Preference objects, allowing a reading app to adapt the type and range of values available in its user interface.

Preference<V>
Preference<Boolean>
EnumPreference<V>