【swift】chartsを使って体重管理アプリを作ってみた

#入力値がグラフに反映される実装をしてみました

流れとしてはこんな感じ

初期画面で体重と体脂肪率を入力⇨登録ボタンで画面遷移、その日の情報が登録されてグラフに表示、グラフタップでその日の情報が表示されるって感じです。

コードメインになります。

メイン画面の方はこんな感じです。

```ruby:main
import UIKit

class HomeViewController: UIViewController,UITextFieldDelegate,UITableViewDelegate,UITableViewDataSource {

    @IBOutlet weak var weightLabel: UITextField!
    @IBOutlet weak var fatLabel: UITextField!
    @IBOutlet weak var datePicker: UIButton!
    @IBOutlet weak var trainingView: UITableView!
    
    //UserDefaultsのインスタンス
    let userDefaults = UserDefaults.standard
    //日付表示変更用
    var changeDate : Date = Date()
    //テーブル表示用
    var trainList = [String]()
    //日付の取得
    let today: Date = Date()
    let dateFormatter = DateFormatter()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //datePickerからの日付を判定し、帰ってきた値をセットする
        dateFormatter.dateFormat = "yyyy/MM/dd"
        if changeDate == today {
            datePicker.setTitle(dateFormatter.string(from: today), for: .normal)
        } else {
            datePicker.setTitle(dateFormatter.string(from: changeDate), for: .normal)
        }
        //トレーニングリストの保存＆呼び出し
        if userDefaults.array(forKey: "training") != nil {
            trainList = userDefaults.array(forKey: "training") as! [String]
            userDefaults.set(trainList, forKey: "training")
            userDefaults.synchronize()
        }
        //Delegate&DataSourceの呼び出し
        trainingView.delegate = self
        trainingView.dataSource = self
        trainingView.allowsMultipleSelection = true
        weightLabel.delegate = self
        fatLabel.delegate = self
    }
    //キーボード以外の箇所のタッチでキーボードを閉じる処理
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.view.endEditing(true)
    }
    //キーボードを閉じる処理
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
    //tableDelegate
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let cell = tableView.cellForRow(at: indexPath)
        cell?.accessoryType = .checkmark
    }
    func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
        let cell = tableView.cellForRow(at:indexPath)
        cell?.accessoryType = .none
    }
    //TableDataSource
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return trainList.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel!.text = trainList[indexPath.row]
        cell.selectionStyle = .none
        let selectedIndexPaths = tableView.indexPathsForSelectedRows
        if selectedIndexPaths != nil && (selectedIndexPaths?.contains(indexPath))! {
            cell.accessoryType = .checkmark
        } else {
            cell.accessoryType = .none
        }
        return cell
    }
    
    @IBAction func saveMemory(_ sender: UIButton) {
        performSegue(withIdentifier: "saveMemory", sender: nil)
    }
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        //入力値に空があればエラーを出す
        if segue.identifier == "saveMemory" {
            let nextView = segue.destination as! ViewController
            //            空の場合は何もせず結果の画面へ遷移
            if weightLabel.text! != "" || fatLabel.text! != "" {
                let xs = self.trainingView.indexPathsForSelectedRows
                if xs != nil {
                    for x in xs! {
                        nextView.trainList.append(trainList[x.row])
                    }
                }
                nextView.weightText = Double(weightLabel.text!)!
                nextView.fatText = Double(fatLabel.text!)!
                nextView.dateText = datePicker.titleLabel!.text!
            }
        }
    }
}
```
下記がチャート画面です。

```ruby:charts
import UIKit
import Charts

class ViewController: UIViewController,ChartViewDelegate,UITableViewDelegate,UITableViewDataSource {
    
    @IBOutlet weak var chartsView: LineChartView!
    @IBOutlet weak var dateLabel: UILabel!
    @IBOutlet weak var weightLabel: UILabel!
    @IBOutlet weak var fatLabel: UILabel!
    @IBOutlet var trainedList: UITableView!
    let userDefaults = UserDefaults.standard
    let weightString = "Weight"
    let fatString = "Fat"
    
    var weightDic = [String:Double]()
    var fatDic = [String:Double]()
    var trainDic = [String:[String]]()
    var weightList = [Double]()
    var fatList = [Double]()
    var weightText = 0.0
    var fatText = 0.0
    var dateText = ""
    var chartDataCount = 0
    var daysList = [String]()
    var trainList = [String]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        chartsView.delegate = self
                
        let weightKey = weightString + dateText
        let fatKey = fatString + dateText
        
        loadData()
        if weightKey != "Weight" || fatKey != "Fat" {
            saveData(weightKey : weightKey, fatKey: fatKey, daysText:dateText)
        } else if dateText == "" {
            let weightSort = weightDic.keys.sorted()
            let fatSort = fatDic.keys.sorted()
            
            for key in weightSort {
                weightList.append(weightDic[key]!)
                daysList.append(String(key.suffix(5)))
            }
            for key in fatSort {
                fatList.append(fatDic[key]!)
            }
        }
        setChart(days:daysList)
        trainList = [String]()
    }
    @IBAction func clearUserDefault(_ sender: Any) {
        print("Clear")
        let alert: UIAlertController = UIAlertController(title: "データ消去", message: "これまでのデータを消去しますか？", preferredStyle: UIAlertController.Style.actionSheet)
        let comit = UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler:{
                (action: UIAlertAction!) -> Void in
            self.userDefaults.removeObject(forKey: "weight")
            self.userDefaults.removeObject(forKey: "fat")
            self.daysList = [""]
            self.weightList = [Double]()
            self.fatList = [Double]()
            self.dateLabel.text! = ""
            self.weightLabel.text! = ""
            self.fatLabel.text! = ""
            self.trainList = [String]()
            self.setChart(days: self.daysList)
            self.trainedList.reloadData()
            super.viewDidLoad()
        })
        let cancel = UIAlertAction(title: "戻る", style: UIAlertAction.Style.cancel, handler: nil)
        alert.addAction(comit)
        alert.addAction(cancel)
        present(alert, animated: true, completion: nil)
    }
    //userDefaultsからデータをDictionaryに代入
    //データがなければスルー(１回目とクリア後)
    func loadData() {
        if userDefaults.dictionary(forKey: "weight") != nil {
            weightDic = userDefaults.dictionary(forKey: "weight") as! [String : Double]
        }
        if userDefaults.dictionary(forKey: "fat") != nil {
            fatDic = userDefaults.dictionary(forKey: "fat") as! [String : Double]
        }
        if userDefaults.dictionary(forKey: "train") != nil {
            trainDic = userDefaults.dictionary(forKey: "train") as! [String:[String]]
        }
    }
    //渡ってきた値をuserDefaultsへセットし、リストへDictionaryのValueをset
    func saveData(weightKey : String,fatKey : String, daysText: String) {
        weightDic[weightKey] = weightText
        fatDic[fatKey] = fatText
        trainDic[daysText] = trainList
        
        userDefaults.set(weightDic, forKey: "weight")
        userDefaults.set(fatDic, forKey: "fat")
        userDefaults.set(trainDic, forKey: "train")
        userDefaults.synchronize()
        
        let weightSort = weightDic.keys.sorted()
        let fatSort = fatDic.keys.sorted()
        
        for key in weightSort {
            weightList.append(weightDic[key]!)
            daysList.append(String(key.suffix(5)))
        }
        for key in fatSort {
            fatList.append(fatDic[key]!)
        }
        setChart(days:daysList)
        trainList = [String]()
    }
    func setChart(days:[String]){
        let lineDefault = UIColor(red: 140.0/255.0, green: 234.0/255.0, blue: 255.0/255.0, alpha: 1.0)
        
        let data = LineChartData()
        var lineChartEntry1 = [ChartDataEntry]()
        
        for i in 0..<weightList.count {
            lineChartEntry1.append(ChartDataEntry(x: Double(i), y: Double(weightList[i])))
        }
        let line1 = LineChartDataSet(entries: lineChartEntry1, label: "体重")
        line1.drawCirclesEnabled = false
        line1.drawValuesEnabled = true
        line1.valueTextColor = UIColor.white
        line1.lineWidth = 2
        line1.setColor(UIColor.red)
        data.addDataSet(line1)
        
        if (fatList.count > 0) {
            var lineChartEntry2 = [ChartDataEntry]()
            for i in 0..<fatList.count {
                lineChartEntry2.append(ChartDataEntry(x: Double(i), y: Double(fatList[i])))
            }
            let line2 = LineChartDataSet(entries: lineChartEntry2, label: "体脂肪")
            line2.drawCirclesEnabled = false
            line2.drawValuesEnabled = true
            line2.valueTextColor = UIColor.white
            line2.lineWidth = 2
            line2.setColor(UIColor.blue)
            data.addDataSet(line2)
        }
        let chartFormatter = LineChartFormatter(labels: days)
        let xAxis = chartsView.xAxis
        xAxis.valueFormatter = chartFormatter
        xAxis.labelFont = UIFont(name: "HelveticaNeue-Light", size: 12.0)!
        xAxis.labelTextColor = UIColor.white
        chartsView.xAxis.granularityEnabled = true
        chartsView.xAxis.granularity = 1.0
        chartsView.xAxis.decimals = 0
        chartsView.xAxis.valueFormatter = xAxis.valueFormatter
        chartsView.data = data
    }
    
    private class LineChartFormatter: NSObject, IAxisValueFormatter {
        
        var labels: [String] = []
        
        func stringForValue(_ value: Double, axis: AxisBase?) -> String {
            if labels.count == 1 {
                return labels[0]
            } else {
                return labels[Int(value)]
            }
        }
        init(labels: [String]) {
            super.init()
            self.labels = labels
        }
    }
    func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) {
        let weightSort = weightDic.keys.sorted()
        let fatSort = fatDic.keys.sorted()
        var weightKey :String
        var fatKey : String
        var dateString : String
        
        if highlight.dataSetIndex == 0 {
            weightKey  = weightSort[NSInteger(entry.x)]
            fatKey = "Fat" + weightSort[NSInteger(entry.x)].suffix(10)
            dateString = String(weightSort[NSInteger(entry.x)].suffix(10))
        } else {
            weightKey  = "Weight" + fatSort[NSInteger(entry.x)].suffix(10)
            fatKey = fatSort[NSInteger(entry.x)]
            dateString = String(fatSort[NSInteger(entry.x)].suffix(10))
        }
        let weightString = weightDic[weightKey]
        let fatString = fatDic[fatKey]
        trainDic = userDefaults.dictionary(forKey: "train") as! [String : [String]]
        print(trainDic)
        if trainDic[dateString] != nil {
            trainList = trainDic[dateString]!
        } else {
            trainList = [String]()
        }
        
        dateLabel.text = dateString
        dateLabel.textColor = UIColor.white
        weightLabel.text = String(weightString!) + "kg"
        weightLabel.textColor = UIColor.white
        fatLabel.text = String(fatString!) + "%"
        fatLabel.textColor = UIColor.white
        trainedList.reloadData()
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return trainList.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel!.text = trainList[indexPath.row]
        return cell
    }
}
```

もっと綺麗なやり方があるんだろうなあ。。。

一応画面です。

<img width="320" alt="IMG_3066.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472705/d2f8e7df-cfe0-6d37-c283-1dce65ea10f3.png">
<img width="320" alt="IMG_3067.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472705/8f9b7ffa-725f-b475-88f4-a635a34e02e1.png">

恥ずかしながらまだSEなんですがｗ

参考になればいいし、もっとこうした方がってのがあれば教えてください！

#profile

生まれも育ちも大阪の浪速中の浪速っ子が30才未経験からITエンジニアとして生きるブログもやってます。
よかったらみてください:muscle_tone2:

<a href="https://blogmura.com/profiles/11016817?p_cid=11016817"><img src="https://blogparts.blogmura.com/parts_image/user/pv11016817.gif" alt="PVアクセスランキング にほんブログ村" /></a>