一个轻量级、模块化且易于扩展的 K 线图表框架,专为 Swift 应用设计。
- 模块化设计:数据加载、指标计算、渲染协调、交互和样式分层管理。
- 高性能渲染:复用 renderer、缓存 legend、合并 redraw,并支持实时 tick 增量更新。
- 扩展友好:可注册指标 renderer,支持自定义样式、主题和指标 key。
- 协议驱动:通过
ChartItemProvider接入历史分页、区间补数和实时流。 - Swift 6 友好:数据提供者和 K 线数据模型要求
Sendable,加载流程使用 actor 隔离。
- K 线蜡烛图绘制(阳线 / 阴线)
- 手势交互(缩放、平移、十字线)
- 实时数据订阅
- 主指标:MA、EMA、WMA、BOLL、SAR
- 副指标:MACD、RSI、VOL
- 分时图模式
- 自定义渲染器
- 主题配置
- 指标选择持久化
- 更多高级指标(持续迭代中)
直接将 Sources/SwiftKLine 目录添加到工程,或在 Swift Package Manager 中引用本仓库。
目前项目处于早期阶段,后续版本可能包含破坏性更改,请谨慎使用。
通过 ChartOptions 配置图表行为,ChartView 负责渲染;数据提供者通过 loadData(using:) 显式绑定或替换:
import UIKit
import SwiftKLine
let provider = BinanceDataProvider(symbol: "BTCUSDT", period: .m1)
let options = ChartOptions(
appearance: .theme(.midnight),
content: .candlestick,
indicators: IndicatorSelectionState(
mainIndicatorIDs: [.ma],
subIndicatorIDs: [.vol, .macd]
),
features: [.liveUpdates, .gapRecovery, .indicatorPersistence],
plugins: .default
)
let chartView = ChartView(options: options)
chartView.loadData(using: provider)
view.addSubview(chartView)ChartItemProvider负责提供分页历史数据、按时间区间补数以及实时流。- 框架内置
ChartItemLoader,会在前后台切换时自动补齐缺失区间。 loadData(using:)会重置当前数据、指标序列和分页状态,并启动新的 loader。ChartOptions负责图表装配;loadData(using:)负责绑定或替换数据提供者。保留这两个步骤可以在周期切换、provider 替换和视图复用时保持行为明确。
chartView.setContentStyle(.candlestick)
chartView.setContentStyle(.timeSeries)ChartType 当前支持:
.candlestick:蜡烛图主图。.timeSeries:分时图主图。
实现 ChartItemProvider 即可接入任意行情源:
final class MyProvider: ChartItemProvider, @unchecked Sendable {
func fetchChartItems(forPage page: Int) async throws -> [any ChartItem] { /* ... */ }
func fetchChartItems(from start: Date, to end: Date) async throws -> [any ChartItem] { /* ... */ }
func liveStream() -> AsyncStream<any ChartItem> { /* 可选 */ }
}说明:
page == 0表示最近一页,page 递增表示继续向更早历史分页。fetchChartItems(from:to:)用于前后台切换、网络恢复等场景的缺口补齐。liveStream()默认返回空流;只有需要实时行情时才需要实现。- Provider 必须是
AnyObject & Sendable。如果内部使用锁、actor 或不可变状态保证线程安全,可按需使用@unchecked Sendable。
K 线数据模型需实现 ChartItem:
struct Candle: ChartItem {
let open: Double
let close: Double
let high: Double
let low: Double
let volume: Double
let value: Double
let timestamp: Int // 秒级时间戳
}ChartView 默认使用 UserDefaultsIndicatorSelectionStore 保存主/副图指标选择。可以注入自定义 store,或传入 nil 关闭持久化:
let view = ChartView(indicatorSelectionStore: nil)监听指标变化:
chartView.indicatorSelectionChanged = { state in
print(state.mainIndicators, state.subIndicators)
}恢复默认指标:
chartView.resetIndicatorsToDefault()let dark = ChartConfiguration.themed(.midnight)
let light = ChartConfiguration.themed(.solaris)
let chartView = ChartView(configuration: dark)let configuration = ChartConfiguration(
candleStyle: CandleStyle(
risingColor: .systemGreen,
fallingColor: .systemRed,
width: 8,
gap: 2
),
watermarkText: "SwiftKLine",
layoutMetrics: LayoutMetrics(
mainChartHeight: 320,
timelineHeight: 16,
indicatorHeight: 72,
indicatorSelectorHeight: 34
),
defaultMainIndicators: [.ma],
defaultSubIndicators: [.vol, .macd]
)通过 PluginRegistry 的 registerRenderer(placement:provider:) 可为指定位置注册额外的渲染器。
let registry = PluginRegistry.default
registry.registerRenderer(placement: .overlay) { _, configuration in
[MyOverlayRenderer()]
}
let options = ChartOptions(plugins: registry)使用已存在的 IndicatorID 注册插件时,会替换当前 PluginRegistry 实例中的旧插件,并返回被替换的插件。不同 chart view 需要不同插件集合时,请使用独立的 registry 实例。
ChartOptions 是主要图表装配对象,ChartConfiguration 是视觉和内置指标展示配置对象。loadData(using:)、setContentStyle(_:)、resetIndicatorsToDefault() 等命令式 API 仍可用。
ChartOptions是主要图表装配对象。ChartConfiguration是视觉和内置指标展示配置对象。- 新代码优先使用
IndicatorSelectionState(mainIndicatorIDs:subIndicatorIDs:),不要优先使用main/sub标签。 ChartItem.timestamp必须是 Unix 秒级时间戳;timestampSeconds别名用于提高可读性。
通过 IndicatorPlugin 和 IndicatorCalculator 协议,可以在不修改内置 BuiltInIndicator 的前提下新增完整指标:
struct MyCalculator: IndicatorCalculator {
let id = SeriesKey(indicatorID: "custom.myIndicator", name: "MY")
func calculate(for items: [any ChartItem]) -> [Double?] {
items.map { Optional($0.close) }
}
}
struct MyPlugin: IndicatorPlugin {
let id: IndicatorID = "custom.myIndicator"
let title = "MY"
let placement: IndicatorPlacement = .overlay
let defaultSeriesKeys = [SeriesKey(indicatorID: "custom.myIndicator", name: "MY")]
func makeCalculators(configuration: ChartConfiguration) -> [any IndicatorCalculator] {
[MyCalculator()]
}
@MainActor func makeRenderers(configuration: ChartConfiguration) -> [any ChartRenderer] {
[MyRenderer()]
}
}
let registry = PluginRegistry.default
registry.register(MyPlugin())
let options = ChartOptions(plugins: registry)本项目采用 MIT 许可证 - 详情请见 LICENSE 文件。