はじめに
この記事は前回の記事から続いています。
前回は株価をタップ&ドラッグした時のアクションを追加しました。
今回はタップした時の上の株価を表示させる&タップしている株価の位置にsymbolを置くの2つをやってみます。
実際の株価アプリ
作成したサンプルアプリ
準備
前前回のDate拡張と同様
モデルの用意
前前回のモデルと同様
実装
struct ContentView: View {
// 土日祝日により欠損していたデータをほn
@State private var stocks: [Stock] = [
.init(date: .init(year: 2023, month: 10, day: 2), value: 2130),
.init(date: .init(year: 2023, month: 10, day: 3), value: 4160),
.init(date: .init(year: 2023, month: 10, day: 4), value: 4308),
.init(date: .init(year: 2023, month: 10, day: 5), value: 5220),
.init(date: .init(year: 2023, month: 10, day: 6), value: 6311),
+ .init(date: .init(year: 2023, month: 10, day: 7), value: 6311),
+ .init(date: .init(year: 2023, month: 10, day: 8), value: 6311),
+ .init(date: .init(year: 2023, month: 10, day: 9), value: 6311),
.init(date: .init(year: 2023, month: 10, day: 10), value: 6311),
.init(date: .init(year: 2023, month: 10, day: 11), value: 5809),
.init(date: .init(year: 2023, month: 10, day: 12), value: 5350),
.init(date: .init(year: 2023, month: 10, day: 13), value: 5196),
+ .init(date: .init(year: 2023, month: 10, day: 14), value: 5196),
+ .init(date: .init(year: 2023, month: 10, day: 15), value: 5196),
.init(date: .init(year: 2023, month: 10, day: 16), value: 5200),
.init(date: .init(year: 2023, month: 10, day: 17), value: 6123),
.init(date: .init(year: 2023, month: 10, day: 18), value: 6110),
.init(date: .init(year: 2023, month: 10, day: 19), value: 6092),
.init(date: .init(year: 2023, month: 10, day: 20), value: 6501),
+ .init(date: .init(year: 2023, month: 10, day: 21), value: 6501),
+ .init(date: .init(year: 2023, month: 10, day: 22), value: 6501),
.init(date: .init(year: 2023, month: 10, day: 23), value: 6867),
.init(date: .init(year: 2023, month: 10, day: 24), value: 7381),
.init(date: .init(year: 2023, month: 10, day: 25), value: 7980),
.init(date: .init(year: 2023, month: 10, day: 26), value: 9016),
.init(date: .init(year: 2023, month: 10, day: 27), value: 8221),
+ .init(date: .init(year: 2023, month: 10, day: 28), value: 8221),
+ .init(date: .init(year: 2023, month: 10, day: 29), value: 8221),
.init(date: .init(year: 2023, month: 10, day: 30), value: 7217),
.init(date: .init(year: 2023, month: 10, day: 31), value: 6221),
]
// 選択している日付
@State private var selectedDate: Date?
+ // 選択している株価
+ @State private var selectedStockValue: Int?
// チャートの色
private var chartColor: Color {
selectedDate != nil ? .cyan : .green
}
var body: some View {
Chart {
ForEach(stocks, id: \.id) { stock in
AreaMark(
x: .value("日時", stock.date, unit: .day, calendar: .autoupdatingCurrent),
y: .value("株価", stock.value)
)
.foregroundStyle(.linearGradient(
.init(colors: [chartColor.opacity(0.3), chartColor.opacity(0.1)]),
startPoint: .top,
endPoint: .bottom
))
LineMark(
x: .value("日時", stock.date, unit: .day, calendar: .autoupdatingCurrent),
y: .value("株価", stock.value)
)
.foregroundStyle(chartColor)
if let selectedDate {
// 日付を選択した時に出てくる縦線
RuleMark(
x: .value("日時", selectedDate)
)
.foregroundStyle(chartColor)
+ .annotation(position: .top) {
+ // 選択している日付の株価を上に表示する
+ Text(selectedStockValue?.description ?? "???")
+ .font(.system(size: 13))
+ .foregroundStyle(chartColor)
+ }
}
+ if let selectedDate, let selectedStockValue {
+ // 折れ線グラフと縦線が重なっているところに表示されている丸
+ PointMark(
+ x: .value("日時", selectedDate),
+ y: .value("株価", selectedStockValue)
+ )
+ .symbol {
+ Circle()
+ .fill()
+ .frame(width: 20, height: 20)
+ .foregroundStyle(chartColor)
+ .shadow(radius: 1)
+ }
+ }
}
}
.frame(height: 250)
+ // チャートのX軸の範囲
+ .chartXScale(domain: Date(year: 2023, month: 10, day: 2) ... Date(year: 2023, month: 10, day: 31))
+ // チャートのY軸の範囲
+ .chartYScale(domain: 0 ... 10000)
.chartXSelection(value: $selectedDate)
.chartGesture { chart in
DragGesture(minimumDistance: 0)
.onChanged {
// ドラッグしている座標をチャートに知らせる
chart.selectXValue(at: $0.location.x)
}
.onEnded { _ in
// 指を離した時にリセット
selectedDate = nil
+ selectedStockValue = nil
}
}
+ .onChange(of: selectedDate) {
+ // nilの場合は無視する
+ guard let selectedDate else { return }
+ // stocksの中で一致する日付を探す
+ selectedStockValue = stocks.first(where: {
+ selectedDate.formatted(.iso8601.year().month().day())
+ == $0.date.formatted(.iso8601.year().month().day())
+ })?.value
+ }
.preferredColorScheme(.dark)
}
}
解説
選択中の日付の株価を格納する変数を作成します。
@State private var selectedStockValue: Int?
選択中の日付の株価にPointMark
を配置します。
symbol
を使うことによって配置するViewを変更することができます。
今回でいうと、この丸いやつです。
if let selectedDate, let selectedStockValue {
// 折れ線グラフと縦線が重なっているところに表示されている丸
PointMark(
x: .value("日時", selectedDate),
y: .value("株価", selectedStockValue)
)
.symbol {
Circle()
.fill()
.frame(width: 20, height: 20)
.foregroundStyle(chartColor)
.shadow(radius: 1)
}
}
RuleMark( /* */ ) {
//
}
.annotation(position: .top) {
// 選択している日付の株価を上に表示する
Text(selectedStockValue?.description ?? "???")
.font(.system(size: 13))
.foregroundStyle(chartColor)
}
チャートのX軸とY軸の表示範囲を指定しています。
今回だとX軸は2023年10月2日~2023年10月31日に、
Y軸は0~10000に設定されています。
Chart {
//
}
// チャートのX軸の範囲
.chartXScale(domain: Date(year: 2023, month: 10, day: 2) ... Date(year: 2023, month: 10, day: 31))
// チャートのY軸の範囲
.chartYScale(domain: 0 ... 10000)
selectedDate
からselectedStockValue
を検索しています。
.onChange(of: selectedDate) {
// nilの場合は無視する
guard let selectedDate else { return }
// stocksの中で一致する日付を探す
selectedStockValue = stocks.first(where: {
selectedDate.formatted(.iso8601.year().month().day())
== $0.date.formatted(.iso8601.year().month().day())
})?.value
}
おわり
だいぶ、本物に近づいてきましたね