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 viamodifiedStudiesSettings
. - 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 Foundationimport 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 Combineimport Foundationfinal class CustomStudiesProvider: DXChart.StudiesProvider {// Limit/override available studies for this provider instance (nil to allow all)let defaultStudiesSettings: [StudySettings.UUID : StudySettings]? = CustomStudiesProvider.defaultStudiesprivate 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.standardprivate 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 indefaultStudiesSettings[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.standardprivate 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.defaultStudiesSettingsprivate(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) } ?? studyIDslet filtered = studyIDs.intersection(allowedIDs)// 2) enforce maximum countlet availableSlots = max(0, maxFavouriteCount - favouriteStudiesSet.count)guard availableSlots > 0 else { return }let toAdd = Set(filtered.prefix(availableSlots))// 3) merge and publishfavouriteStudiesSet = 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.standardprivate 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.defaultStudiesSettingsprivate(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) } ?? studyIDslet filtered = studyIDs.intersection(allowedIDs)// 2) enforce maximum countlet availableSlots = max(0, maxFavouriteCount - favouriteStudiesSet.count)guard availableSlots > 0 else { return }let toAdd = Set(filtered.prefix(availableSlots))// 3) merge and publishfavouriteStudiesSet = 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// orlet defaultStudiesSettings: [StudySettings.UUID : StudySettings]? = StudySettings.defaultStudiesSettings
Replace an indicator’s default settings with custom settings
let defaultStudiesSettings: [StudySettings.UUID : StudySettings]? = StudySettings.defaultStudiesSettings.map { studySettings inguard studySettings.id == .sma else { return studySettings }var customSMA = studySettingscustomSMA.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.adxstudy.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 propertyvar defaultStudiesSettings: [StudySettings.UUID : StudySettings]? = {var studies = StudySettings.defaultStudiesSettingslet customADX = StudySettings.customADXstudies[customADX.uuid] = customADXreturn studies}()
Tips
- Start from defaults: Use
StudySettings.defaultStudiesSettings
as a base, then selectively override properties (e.g.,title
, line colors, parameter values) before exposing viadefaultStudiesSettings
. - 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 linetitle
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.