Skip to main content

Studies Provider

The StudiesProvider protocol defines how the host app configures and persists user-visible Indicators (Studies): which studies are available, their default settings, the current user-modified settings, and the favourites list. Implement it to fully control the Indicators module behaviour and persistence model in your app.

Interfaces

  • defaultStudiesSettings: Optional dictionary of allowed studies with their default settings. When nil, all built-in studies are available. Provide a subset to restrict availability or override defaults.
  • modifiedStudiesSettings: Publisher of the current studies dictionary keyed by StudySettings.UUID. Emit the full dictionary and replay the latest to new subscribers.
  • updateStudySettings(_:): Upsert a single StudySettings and publish the new dictionary via modifiedStudiesSettings.
  • favouriteStudies: Publisher of the current favourites set. Emit the full set and replay.
  • setStudiesFavourite(:) / setStudiesUnfavourite(:): Mutate the favourites set and publish via favouriteStudies.

Protocol

import Foundation
import Combine
/// A lightweight configuration surface for the Indicators (Studies) module.
///
/// Use this provider to control which indicators are available to the user in the
/// Indicators/Studies flow.
public protocol StudiesProvider {
/// Dictionary of default and allowed study settings to present in the UI, keyed by UUID.
///
/// Provide the baseline set of studies available to users. You may also supply
/// customized default settings for any study (derived from the library defaults).
/// Use `StudySettings.defaultStudiesSettings` to inspect the full list of base studies.
/// - Note: Pass `nil` to allow all available indicators.
var defaultStudiesSettings: [StudySettings.UUID : StudySettings]? { get }
/// Publisher of the modifiedStudiesSettings, current dictionary of study settings keyed by UUID.
///
/// Implementations should emit the entire dictionary whenever it changes and
/// replay the latest value to new subscribers (e.g. use a replaying subject).
var modifiedStudiesSettings: AnyPublisher<[StudySettings.UUID : StudySettings], Never> { get }
/// Updates a single study settings entry in the current dictionary and publishes changes.
/// - Parameter studySettings: Study settings to upsert, identified by its `uuid`.
func updateStudySettings(_ studySettings: StudySettings)
/// Publisher of the current set of favourite study identifiers.
///
/// Provide a publisher that always reflects the up-to-date favourites.
/// - Recommendations:
/// - Use a replaying publisher (e.g. `CurrentValueSubject`) so new subscribers
/// immediately receive the latest favourites.
/// - Emit the full set on every change (not deltas).
var favouriteStudies: AnyPublisher<Set<StudySettings.UUID>, Never> { get }
/// Adds the given studies to the favourites list.
///
/// Implementations should merge `studyIDs` into the existing favourites set and
/// publish the updated set via `favouriteStudies`.
/// - Parameters:
/// - studyIDs: The identifiers to mark as favourite. Duplicates are ignored.
func setStudiesFavourite(_ studyIDs: Set<DXChart.StudySettings.UUID>)
/// Removes the given studies from the favourites list.
///
/// Implementations should remove `studyIDs` from the existing favourites set and
/// publish the updated set via `favouriteStudies`.
/// - Parameters:
/// - studyIDs: The identifiers to unmark as favourite. Unknown IDs are ignored.
func setStudiesUnfavourite(_ studyIDs: Set<DXChart.StudySettings.UUID>)
}

Example implementation

import Combine
import Foundation
final class CustomStudiesProvider: DXChart.StudiesProvider {
// Limit/override available studies for this provider instance (nil to allow all)
let defaultStudiesSettings: [StudySettings.UUID : StudySettings]? = CustomStudiesProvider.defaultStudies
private lazy var _studiesSettings = CurrentValueSubject<[StudySettings.UUID : StudySettings], Never>(storedStudiesSettings)
var modifiedStudiesSettings: AnyPublisher<[StudySettings.UUID : StudySettings], Never> {
_studiesSettings.eraseToAnyPublisher()
}
func updateStudySettings(_ settings: StudySettings) {
storedStudiesSettings[settings.uuid] = settings
_studiesSettings.send(storedStudiesSettings)
}
private let storage: StorageManager = UserDefaults.standard
private let favouriteStudiesKey = "favouriteStudiesKey"
private let studiesSettingsKey = "studiesSettingsKey"
let defaultFavourites: [DXChart.StudySettings] = [
.sma,
.relativeStrengthIndex,
.macd,
.aroonOscillator,
.bollingerBands,
.ichimoku,
.linearRegressionChannel,
.vwap
]
private(set) var favouriteStudiesSet: Set<DXChart.StudySettings.UUID> {
get {
do {
return try storage.getObject(forKey: favouriteStudiesKey, castTo: Set<DXChart.StudySettings.UUID>.self)
} catch {
return Set(defaultFavourites.compactMap { $0.uuid })
}
}
set {
do {
try storage.setObject(newValue, forKey: favouriteStudiesKey)
} catch {
print("can't save object (error)")
}
}
}
lazy var _favouriteStudies: CurrentValueSubject<Set<DXChart.StudySettings.UUID>, Never> = .init(favouriteStudiesSet)
var favouriteStudies: AnyPublisher<Set<DXChart.StudySettings.UUID>, Never> {
_favouriteStudies.eraseToAnyPublisher()
}
func setStudiesFavourite(_ studyIDs: Set<DXChart.StudySettings.UUID>) {
favouriteStudiesSet = favouriteStudiesSet.union(studyIDs)
_favouriteStudies.send(favouriteStudiesSet)
}
func setStudiesUnfavourite(_ studyIDs: Set<DXChart.StudySettings.UUID>) {
favouriteStudiesSet = favouriteStudiesSet.subtracting(studyIDs)
_favouriteStudies.send(favouriteStudiesSet)
}
private var storedStudiesSettings: [StudySettings.UUID : StudySettings] {
get {
do {
let studiesSettings = try storage.getObject(forKey: studiesSettingsKey, castTo: [StudySettings.UUID : StudySettings].self)
if let defaultStudiesSettings {
let availableStudies = studiesSettings.filter { uuid, study in
defaultStudiesSettings[uuid] != nil
}
return availableStudies
}
return studiesSettings
} catch {
return defaultStudiesSettings ?? StudySettings.defaultStudiesSettings.values.uuidDictionary()
}
}
set {
do {
try storage.setObject(newValue, forKey: studiesSettingsKey)
} catch {
debugPrint("can't save object (error)")
}
}
}
}

Favourites

You fully control the favourites list through favouriteStudies, setStudiesFavourite(_:), and setStudiesUnfavourite(_:).

  • Define defaults via your storage or a fallback list.
  • Constrain which studies can be favourited (e.g., only from defaultStudiesSettings).
  • Enforce additional rules such as a maximum count or disallowing specific studies.

Example: constrain favourites to allowed studies and a maximum count

final class ConstrainedFavouritesProvider: DXChart.StudiesProvider {
// ... other requirements (defaultStudiesSettings, modifiedStudiesSettings, updateStudySettings, etc.)
private let storage: StorageManager = UserDefaults.standard
private let favouriteStudiesKey = "favouriteStudiesKey"
private let maxFavouriteCount = 10
/// Optionally restrict favouriting to the currently allowed studies
/// (keys of defaultStudiesSettings). If nil, all known studies may be favourited.
let defaultStudiesSettings: [StudySettings.UUID : StudySettings]? = StudySettings.defaultStudiesSettings
private(set) var favouriteStudiesSet: Set<DXChart.StudySettings.UUID> {
get {
(try? storage.getObject(forKey: favouriteStudiesKey, castTo: Set<DXChart.StudySettings.UUID>.self))
?? []
}
set {
try? storage.setObject(newValue, forKey: favouriteStudiesKey)
}
}
private lazy var _favouriteStudies = CurrentValueSubject<Set<DXChart.StudySettings.UUID>, Never>(favouriteStudiesSet)
var favouriteStudies: AnyPublisher<Set<DXChart.StudySettings.UUID>, Never> { _favouriteStudies.eraseToAnyPublisher() }
func setStudiesFavourite(_ studyIDs: Set<DXChart.StudySettings.UUID>) {
// 1) keep only allowed study IDs (if restriction is configured)
let allowedIDs: Set<DXChart.StudySettings.UUID> = defaultStudiesSettings.map { Set($0.keys) } ?? studyIDs
let filtered = studyIDs.intersection(allowedIDs)
// 2) enforce maximum count
let availableSlots = max(0, maxFavouriteCount - favouriteStudiesSet.count)
guard availableSlots > 0 else { return }
let toAdd = Set(filtered.prefix(availableSlots))
// 3) merge and publish
favouriteStudiesSet = favouriteStudiesSet.union(toAdd)
_favouriteStudies.send(favouriteStudiesSet)
}
func setStudiesUnfavourite(_ studyIDs: Set<DXChart.StudySettings.UUID>) {
favouriteStudiesSet = favouriteStudiesSet.subtracting(studyIDs)
_favouriteStudies.send(favouriteStudiesSet)
}
}

Favourites

You fully control the favourites list through favouriteStudies, setStudiesFavourite(_:), and setStudiesUnfavourite(_:).

  • Define defaults via your storage or a fallback list.
  • Constrain which studies can be favourited (e.g., only from defaultStudiesSettings).
  • Enforce additional rules such as a maximum count or disallowing specific studies.

Example: constrain favourites to allowed studies and a maximum count

final class ConstrainedFavouritesProvider: DXChart.StudiesProvider {
// ... other requirements (defaultStudiesSettings, modifiedStudiesSettings, updateStudySettings, etc.)
private let storage: StorageManager = UserDefaults.standard
private let favouriteStudiesKey = "favouriteStudiesKey"
private let maxFavouriteCount = 10
/// Optionally restrict favouriting to the currently allowed studies
/// (keys of defaultStudiesSettings). If nil, all known studies may be favourited.
let defaultStudiesSettings: [StudySettings.UUID : StudySettings]? = StudySettings.defaultStudiesSettings
private(set) var favouriteStudiesSet: Set<DXChart.StudySettings.UUID> {
get {
(try? storage.getObject(forKey: favouriteStudiesKey, castTo: Set<DXChart.StudySettings.UUID>.self))
?? []
}
set {
try? storage.setObject(newValue, forKey: favouriteStudiesKey)
}
}
private lazy var _favouriteStudies = CurrentValueSubject<Set<DXChart.StudySettings.UUID>, Never>(favouriteStudiesSet)
var favouriteStudies: AnyPublisher<Set<DXChart.StudySettings.UUID>, Never> { _favouriteStudies.eraseToAnyPublisher() }
func setStudiesFavourite(_ studyIDs: Set<DXChart.StudySettings.UUID>) {
// 1) keep only allowed study IDs (if restriction is configured)
let allowedIDs: Set<DXChart.StudySettings.UUID> = defaultStudiesSettings.map { Set($0.keys) } ?? studyIDs
let filtered = studyIDs.intersection(allowedIDs)
// 2) enforce maximum count
let availableSlots = max(0, maxFavouriteCount - favouriteStudiesSet.count)
guard availableSlots > 0 else { return }
let toAdd = Set(filtered.prefix(availableSlots))
// 3) merge and publish
favouriteStudiesSet = favouriteStudiesSet.union(toAdd)
_favouriteStudies.send(favouriteStudiesSet)
}
func setStudiesUnfavourite(_ studyIDs: Set<DXChart.StudySettings.UUID>) {
favouriteStudiesSet = favouriteStudiesSet.subtracting(studyIDs)
_favouriteStudies.send(favouriteStudiesSet)
}
}

Study availability

Full list of default studies

let defaultStudiesSettings: [StudySettings.UUID : StudySettings]? = nil
// or
let defaultStudiesSettings: [StudySettings.UUID : StudySettings]? = StudySettings.defaultStudiesSettings

Replace an indicator’s default settings with custom settings

let defaultStudiesSettings: [StudySettings.UUID : StudySettings]? = StudySettings.defaultStudiesSettings.map { studySettings in
guard studySettings.id == .sma else { return studySettings }
var customSMA = studySettings
customSMA.title = "Custom SMA"
customSMA.parameters = [/* Your custom parameters */]
return customSMA
}

Add custom study settings (based on a default study only)

extension StudySettings {
static let customADX: StudySettings = {
var study = StudySettings.adx
study.title = "Custom ADX"
study.uuid = "customADX"
study.parameters = [
StudySettings.Parameter(
id: "length",
title: "Length",
studyParamType: .intRange,
value: "15",
validationTO: StudySettings.Validation(min: Float(1.0), max: nil, precision: 0)
),
StudySettings.Parameter(
id: "average",
title: "Average",
studyParamType: .average,
value: "WILDERS",
validationTO: nil
)
]
return study
}()
}
// Custom StudiesProvider property
var defaultStudiesSettings: [StudySettings.UUID : StudySettings]? = {
var studies = StudySettings.defaultStudiesSettings
let customADX = StudySettings.customADX
studies[customADX.uuid] = customADX
return studies
}()

Tips

  • Start from defaults: Use StudySettings.defaultStudiesSettings as a base, then selectively override properties (e.g., title, line colors, parameter values) before exposing via defaultStudiesSettings.
  • Restrict availability: Provide a filtered dictionary for defaultStudiesSettings to hide certain indicators from the UI.
  • Custom display names: Change StudySettings.title to present a custom indicator name in the UI. You can also customize parameter and line title fields for user-facing labels.
  • Replay latest values: Back your publishers with CurrentValueSubject (or any replaying subject) so that the UI immediately receives the latest state after subscription.
  • Emit full snapshots: Always send full dictionaries/sets on change (not deltas) to simplify UI updates and avoid merge issues.