カテゴリー: iOS

SwiftUIでChartライブラリを使ってみた。

はじめに

こんにちはsuzukiです。本日はSwiftUIで、Chartライブラリを使う方法についてまとめました。

Chartsライブラリについて

Swift UIでChartsを利用してチャートを書きます。
以前の記事で何度か触れている、無料でチャート描画が可能なライブラリです。
サンプルはiOSはGitHub上のdemoから確認できます。

ライブラリの導入

今回はCocoaPodsを利用しインストールしました。
pod 'Charts'
上記をPodfileに記述し、pod installを行います。

wrapper structの作成

SwiftUIでChartライブラリを利用する手順はこちらでも説明がございます。
SwiftUIでチャートの描画を行うためにラッパークラスを作成します。※structなので、クラスという表現は間違っているかもしれませんが、ラッパーストラクトとかラッパー構造体に違和感があり、ラッパークラスと表現してます。
今回はCandleStickChartViewというローソク足のラッパークラスを作成します。

描画のための準備

下記手順で描画するための準備を行います。
1、クラスファイルの追加
 今回はCandleという名前でファイルの追加
2、ライブラリのインポート分の描画 

import Charts
import SwiftUI

3、Candleという構造体を定義

struct Candle: UIViewRepresentable {
    //描画するViewのエイリアスを設定
    typealias UIViewType = CandleStickChartView
    //UIViewRepresentableプロトコル Chartsフレームワークを返却(エイリアスで記述したクラス)
    func makeUIView(context: UIViewRepresentableContext<Candle>) -> CandleStickChartView {
        
    }
    //UIViewRepresentableプロトコル チャートの入力データが変更された場合にビューを更新するコードを記述
    func updateUIView(_ uiView: CandleStickChartView, context: UIViewRepresentableContext<Candle>) {
        
    }
}

4、makeUIViewでCandleStickChartViewを返却

    func makeUIView(context: UIViewRepresentableContext<Candle>) -> CandleStickChartView {
        let chart = CandleStickChartView()
        return chart
    }

ここまででSwiftUIで画面上に配置する準備ができました。試しにCandle()を呼び出すと、No chart data availableと表示されます。

データの取得と更新

データの取得と更新について、詳細は以前のSwift ChartsライブラリとCryptowatchAPIでローソク足を描画してみる。の記事で行ったことを元に移植しています。
1、通信のレスポンス用の構造体を定義

//一月ごとのデータ取得
struct MonthOHLCListResult: Codable{
    let result: OHLCList
}


struct OHLCList: Codable{
    let list: [[Double]]
    private enum CodingKeys: String, CodingKey {
        case list = "86400"//ここで取得データの範囲を決定
    }
}

2、CryptowatchAPIでからOHLCデータを取得

    func fetchOHLCData(completionHandler: @escaping (OHLCList) -> Void ){
        guard let url = URL(string: "https://api.cryptowat.ch/markets/coinbase-pro/btcusd/ohlc?periods=86400") else {
            return
        }
        URLSession(configuration: .default).dataTask(with: url) { (data, response, error) in
            guard let data = data else{
                return
            }
            //JSONDecoder
            let decoder = JSONDecoder()
            //JSONDecoderでレスポンス→MonthOHLCListResultクラスとして変換
            if let response = try? decoder.decode(MonthOHLCListResult.self, from: data){
                print(response.result)
                DispatchQueue.main.async{
                    completionHandler(response.result)
                }
            }
        }.resume()
    }

3、OHLCデータからローソク足用のデータを作成
チャートの描画用の範囲などは要調整です。

    func generateCandleData(_ ohlcList: OHLCList) -> CandleChartData{
        let candleChartDataEntries:[CandleChartDataEntry] = ohlcList.list.enumerated().compactMap{
            return CandleChartDataEntry(x: Double($0.offset), shadowH: $0.element[2], shadowL: $0.element[2], open: $0.element[1], close: $0.element[4])
            
        }
        let set = CandleChartDataSet(entries: candleChartDataEntries, label: "Candle DataSet")
        set.setColor(.lightText)
        set.decreasingColor = .blue
        set.decreasingFilled = true
        set.increasingColor = .red
        set.increasingFilled = true
        set.shadowColor = .darkGray
        set.valueFont = .systemFont(ofSize: 10)
        set.drawValuesEnabled = false
        return CandleChartData(dataSet: set)
    }

4、updateUIViewで上記を使用

    //UIViewRepresentableプロトコル チャートの入力データが変更された場合にビューを更新するコードを記述
    func updateUIView(_ uiView: CandleStickChartView, context: UIViewRepresentableContext<Candle>) {
        fetchOHLCData { ohlc in
            uiView.data =  self.generateCandleData(ohlc)
        }
    }

5、Candle()を使用してチャートを表示

struct ContentView: View {
    var body: some View {
        VStack{
            Candle()
        }
    }
}

表示領域が調整が微妙ですが、拡大すると下記のスクショのようにローソク足が表示されています。

コード全体

//インポート文の追加
import Charts
import SwiftUI

//wrapper structの追加

struct Candle: UIViewRepresentable {
    //描画するViewのエイリアスを設定
    typealias UIViewType = CandleStickChartView
    //UIViewRepresentableプロトコル Chartsフレームワークを返却(エイリアスで記述したクラス)
    func makeUIView(context: UIViewRepresentableContext<Candle>) -> CandleStickChartView {
        let chart = CandleStickChartView()
        return chart
    }
    //UIViewRepresentableプロトコル チャートの入力データが変更された場合にビューを更新するコードを記述
    func updateUIView(_ uiView: CandleStickChartView, context: UIViewRepresentableContext<Candle>) {
        fetchOHLCData { ohlc in
            uiView.data =  self.generateCandleData(ohlc)
        }
    }
    func generateCandleData(_ ohlcList: OHLCList) -> CandleChartData{
        let candleChartDataEntries:[CandleChartDataEntry] = ohlcList.list.enumerated().compactMap{
            return CandleChartDataEntry(x: Double($0.offset), shadowH: $0.element[2], shadowL: $0.element[2], open: $0.element[1], close: $0.element[4])
            
        }
        let set = CandleChartDataSet(entries: candleChartDataEntries, label: "Candle DataSet")
        set.setColor(.lightText)
        set.decreasingColor = .blue
        set.decreasingFilled = true
        set.increasingColor = .red
        set.increasingFilled = true
        set.shadowColor = .darkGray
        set.valueFont = .systemFont(ofSize: 10)
        set.drawValuesEnabled = false
        return CandleChartData(dataSet: set)
    }
    func fetchOHLCData(completionHandler: @escaping (OHLCList) -> Void ){
        guard let url = URL(string: "https://api.cryptowat.ch/markets/coinbase-pro/btcusd/ohlc?periods=86400") else {
            return
        }
        URLSession(configuration: .default).dataTask(with: url) { (data, response, error) in
            guard let data = data else{
                return
            }
            //JSONDecoder
            let decoder = JSONDecoder()
            //JSONDecoderでレスポンス→MonthOHLCListResultクラスとして変換
            if let response = try? decoder.decode(MonthOHLCListResult.self, from: data){
                print(response.result)
                DispatchQueue.main.async{
                    completionHandler(response.result)
                }
            }
        }.resume()
    }
    

}

//一月ごとのデータ取得
struct MonthOHLCListResult: Codable{
    let result: OHLCList
}


struct OHLCList: Codable{
    //本来であれば別クラスを定義すべきかもしれませんが、処理的にもあまり多くしたくないため[[Double]]としてそのまま利用
    let list: [[Double]]
    private enum CodingKeys: String, CodingKey {
        case list = "86400"
    }
}

//ContentView
struct ContentView: View {
    var body: some View {
        VStack{
            Candle()
        }
    }
}

さいごに

最後までありがとうございます。想像していたよりは簡単に、ライブラリのViewを元にSwiftUIが利用できました。
実際に使うとなると更新タイミングどうしようとかまだ考えることはありますが、今回の記事は以上となります。XCodeのBetaがもう少しうまく動いたら、またwidget周りを触っていこうと思います。

おすすめ書籍

suzuki

シェア
執筆者:
suzuki
タグ: SwiftSwiftUI

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

2週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

4週間 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前