iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 28
0
Software Development

利用Swift 4開發iOS App,Daily Work List系列 第 28

Day 28. Develop Chart View Controller

  • 分享至 

  • xImage
  •  

這個View Controller的重點在於畫出圖表,因此我著重分享Charts的應用,文末再附上整個View Controller的程式碼供參考喔!

在開發這個App的同時,也是讓自己練習使用swift與不同套件,所以這邊我刻意挑三種不同的圖表型態來操作

BarChartView 長條圖

// 新增一個長條圖
let dayMoodChart = BarChartView(frame: CGRect(x: x, y: 10, width: viewWidth, height: chartsHeight))
// 當沒有資料的時候顯示的文字
dayMoodChart.noDataText = "You Need to Provide Your Daily Events."
// 此長條圖的Data Set為Mood Entries
let moodChartDataSet = BarChartDataSet(values: moodEntries, label: "Mood")
// 長條圖顏色為紅色
moodChartDataSet.colors = [UIColor.red]
// 不顯示每根長條的數值
moodChartDataSet.drawValuesEnabled = false
// 將長條圖的Data Set加入Chart Data
let chartData = BarChartData(dataSet: moodChartDataSet)
// 指定Chart的Data
dayMoodChart.data = chartData
// Chart的說明文字為空,看起來即為不顯示
dayMoodChart.chartDescription?.text = ""
// 不顯示右側座標欄位
dayMoodChart.rightAxis.enabled = false
// x軸座標值顯示於下方
dayMoodChart.xAxis.labelPosition = .bottom
// y軸的座標範圍為0-6
dayMoodChart.setVisibleYRange(minYRange: 0.0, maxYRange: 6.0, axis: YAxis.AxisDependency.right)
// y軸的座標值區分為6個標籤
dayMoodChart.leftAxis.labelCount = 6
// 顯示圖表時以1秒鐘方式動畫呈現
dayMoodChart.animate(xAxisDuration: 1.0)

CombinedChartView 複合圖

let dayWaterExerciseChart = CombinedChartView(frame: CGRect(x: x, y: chartsHeight + 50, width: viewWidth, height: chartsHeight))
// 當沒有資料的時候顯示的文字
dayWaterExerciseChart.noDataText = "You Need to Provide Your Daily Events."
// 喝水量的Data Set為water Entries
let waterChartDataSet = BarChartDataSet(values: waterEntries, label: "Water")
// 顏色為藍色
waterChartDataSet.colors = [UIColor.blue]
// 不顯示每個的數值
waterChartDataSet.drawValuesEnabled = false
// 將喝水量的Data Set加入Bar Chart Data(長條圖示)
let barChartData = BarChartData(dataSet: waterChartDataSet)
// 運動量的Data Set為exercise Entries
let exerciseChartDataSet = LineChartDataSet(values: exerciseEntries, label: "Exercise")
// 線的顏色為棕色
exerciseChartDataSet.colors = [UIColor.brown]
// 不顯示每個的數值
exerciseChartDataSet.drawValuesEnabled = false
// 每個圓圈圈點的顏色為棕色
exerciseChartDataSet.circleColors = [UIColor.brown]
// 圓圈半徑為3
exerciseChartDataSet.circleRadius = 3
// 將運動量的Data Set加入Line Chart Data(折線圖)
let lineChartData = LineChartData(dataSet: exerciseChartDataSet)
// 將喝水量與運動量的ChartData指向
let combinedChartData = CombinedChartData()
combinedChartData.barData = barChartData
combinedChartData.lineData = lineChartData
// 指定Chart的Data
dayWaterExerciseChart.data = combinedChartData
// Chart的說明文字為空,看起來即為不顯示
dayWaterExerciseChart.chartDescription?.text = ""
// 不顯示右側座標欄位
dayWaterExerciseChart.rightAxis.enabled = false
// x軸座標值顯示於下方
dayWaterExerciseChart.xAxis.labelPosition = .bottom
// y軸的座標範圍為0-6
dayWaterExerciseChart.setVisibleYRange(minYRange: 0.0, maxYRange: 6.0, axis: YAxis.AxisDependency.right)
// y軸的座標值區分為6個標籤
dayWaterExerciseChart.leftAxis.labelCount = 6
// 顯示圖表時以1秒鐘方式動畫呈現
dayWaterExerciseChart.animate(xAxisDuration: 1.0)

PieChartView

let eventCategoryChart = PieChartView(frame: CGRect(x: x, y: 10, width: viewWidth, height: chartsHeight*2))
// 當沒有資料的時候顯示的文字
eventCategoryChart.noDataText = "You Need to Provide Your Daily Events."
if categoryEntries.count > 0 {
    // Chart的說明文字為空,看起來即為不顯示
    eventCategoryChart.chartDescription?.text = ""
    // Event類別的Data Set為category Entries
    let chartDataSet = PieChartDataSet(values: categoryEntries, label: "")
    // 將Data Set 加入Pie Chart Data(圓餅圖)
    let chartData = PieChartData(dataSet: chartDataSet)
    // 利用亂數隨機給予每個圓餅區塊顏色
    var colors: [UIColor] = []
    for _ in 0..<categoryEntries.count {
        colors.append(UIColor.init(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 1.0))
    }
    chartDataSet.colors = colors
    // 指定Chart的Data
    eventCategoryChart.data = chartData
}

依照月份撈取對應資料

當我們切換月份的時候,要更新圖表內容,因此需要去update每個資料的Entries,然後重新繪製圖表(drawCharts())

func changeMonth(value: Int) {
    currentMonth = currentMonth + value
    if currentMonth > 12 {
        currentMonth = 1
        currentYear = currentYear + 1
    } else if currentMonth < 1 {
        currentMonth = 12
        currentYear = currentYear - 1
    }
    
    calendarTitle.text = "\(currentYear) " + monthTitle[currentMonth - 1]
    
    let dayChangeDict = sqlManager.queryDayByMonth(month: currentYM)
    moodEntries.removeAll()
    waterEntries.removeAll()
    exerciseEntries.removeAll()
    for i in 1...daysCount {
        var date: String!
        if i < 10 {
            date = "\(currentYM)0\(i)"
        } else {
            date = "\(currentYM)\(i)"
        }
        moodEntries.append(BarChartDataEntry(x: Double(i), y: Double(dayChangeDict[date]?.mood ?? 0)))
        waterEntries.append(BarChartDataEntry(x: Double(i), y: Double(dayChangeDict[date]?.water ?? 0)))
        exerciseEntries.append(ChartDataEntry(x: Double(i), y: Double(dayChangeDict[date]?.exercise ?? 0)))
    }
    
    let eventCategoryDict = sqlManager.queryEventCategoryByMonth(month: currentYM)
    categoryEntries.removeAll()
    for (k, v) in eventCategoryDict {
        categoryEntries.append(PieChartDataEntry(value: Double(v), label: categoryDict[k]))
    }

    drawCharts()
}

SQLiteManager.swift加入的查詢功能

func queryDayByMonth(month: String) -> [String : Day] {
    var dayList: [String : Day] = [String : Day]()
    do {
        for result in Array(try database.prepare(TB_DAY.filter(TB_DAY_WORK_ID.like("\(month)%")).order(TB_DAY_WORK_ID))) {
            dayList[result[TB_DAY_WORK_ID]] = Day(work_id: result[TB_DAY_WORK_ID], mood: result[TB_DAY_MOOD], water: result[TB_DAY_WATER], exercise: result[TB_DAY_EXERCISE], note: result[TB_DAY_NOTE] ?? "")
        }
    } catch {
    }
    return dayList
}

func queryEventCategoryByMonth(month: String) -> [Int : Int] {
    var category: [Int : Int] = [Int : Int]()
    do {
        for result in try database.prepare("SELECT category, count(1) FROM TB_EVENT where SUBSTR(day, 1, 6) = ? GROUP BY category", month) {
            category[Int(truncatingIfNeeded: result[0] as! Int64)] = Int(truncatingIfNeeded: result[1] as! Int64)
        }
    } catch {
    }
    return category
}

依照這樣個邏輯就可以完成了喔!顯示結果如圖片

https://ithelp.ithome.com.tw/upload/images/20181028/201119163veLOBiR41.png https://ithelp.ithome.com.tw/upload/images/20181028/20111916wzTkVxhXt2.png
https://ithelp.ithome.com.tw/upload/images/20181028/20111916L0o8Ncg9xl.png https://ithelp.ithome.com.tw/upload/images/20181028/20111916O0zdwOQXuh.png

完整程式碼供參:

//
//  ChartViewController.swift
//  DailyWorkList
//
//  Created by poyuchen on 2018/10/24.
//  Copyright © 2018年 poyu. All rights reserved.
//

import Foundation
import UIKit
import Charts

class ChartViewController: UIViewController {
    let sqlManager = (UIApplication.shared.delegate as! AppDelegate).sqlManager
    
    @IBOutlet weak var showView: UIView!
    var chartsHeight: Int = 500
    let spacesHeight: Int = 200
    let x: Int = 0
    var viewWidth: Int = 0
    
    @IBOutlet weak var calendarTitle: UILabel!
    @IBOutlet weak var chartsType: UISegmentedControl!
    
    let monthTitle = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
    var currentYear = Calendar.current.component(.year, from: Date())
    var currentMonth = Calendar.current.component(.month, from: Date())
    var currentYM: String {
        if currentMonth < 10 {
            return "\(currentYear)0\(currentMonth)"
        } else {
            return "\(currentYear)\(currentMonth)"
        }
    }
    var daysCount: Int {
        // 設定目前月份
        let dateComponents = DateComponents(year: currentYear, month: currentMonth)
        let date = Calendar.current.date(from: dateComponents)!
        // 取得該月份天數
        return Calendar.current.range(of: .day, in: .month, for: date)?.count ?? 0
    }
    
    var moodEntries: [BarChartDataEntry] = []
    var waterEntries: [BarChartDataEntry] = []
    var exerciseEntries: [ChartDataEntry] = []
    var categoryEntries: [PieChartDataEntry] = []
    var categoryDict: [Int : String] = [:]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        viewWidth = Int(showView.frame.width)
        chartsHeight = Int(showView.frame.height) / 2 - 50
        
        if let path = Bundle.main.path(forResource: "Category", ofType: "plist"),
            let array = NSArray(contentsOfFile: path) {
            // Use your myDict here
            for case let category as NSDictionary in array {
                categoryDict[category.object(forKey: "code") as! Int] = category.object(forKey: "name") as? String
            }
        }
        
        changeMonth(value: 0)
    }
    
    @IBAction func changeCharts(_ sender: UISegmentedControl) {
        drawCharts()
    }
    
    @IBAction func previousMonth(_ sender: UIButton) {
        changeMonth(value: -1)
    }
    
    @IBAction func nextMonth(_ sender: UIButton) {
        changeMonth(value: 1)
    }
    
    func changeMonth(value: Int) {
        currentMonth = currentMonth + value
        if currentMonth > 12 {
            currentMonth = 1
            currentYear = currentYear + 1
        } else if currentMonth < 1 {
            currentMonth = 12
            currentYear = currentYear - 1
        }
        
        calendarTitle.text = "\(currentYear) " + monthTitle[currentMonth - 1]
        
        let dayChangeDict = sqlManager.queryDayByMonth(month: currentYM)
        moodEntries.removeAll()
        waterEntries.removeAll()
        exerciseEntries.removeAll()
        for i in 1...daysCount {
            var date: String!
            if i < 10 {
                date = "\(currentYM)0\(i)"
            } else {
                date = "\(currentYM)\(i)"
            }
            moodEntries.append(BarChartDataEntry(x: Double(i), y: Double(dayChangeDict[date]?.mood ?? 0)))
            waterEntries.append(BarChartDataEntry(x: Double(i), y: Double(dayChangeDict[date]?.water ?? 0)))
            exerciseEntries.append(ChartDataEntry(x: Double(i), y: Double(dayChangeDict[date]?.exercise ?? 0)))
        }
        
        let eventCategoryDict = sqlManager.queryEventCategoryByMonth(month: currentYM)
        categoryEntries.removeAll()
        for (k, v) in eventCategoryDict {
            categoryEntries.append(PieChartDataEntry(value: Double(v), label: categoryDict[k]))
        }

        drawCharts()
    }
    
    func drawCharts() {
        showView.subviews.forEach({ $0.removeFromSuperview()})
        if chartsType.selectedSegmentIndex == 0 {
            // 心情 長條圖
            let dayMoodChart = BarChartView(frame: CGRect(x: x, y: 10, width: viewWidth, height: chartsHeight))
            dayMoodChart.noDataText = "You Need to Provide Your Daily Events."
            
            let moodChartDataSet = BarChartDataSet(values: moodEntries, label: "Mood")
            moodChartDataSet.colors = [UIColor.red]
            moodChartDataSet.drawValuesEnabled = false
            
            let chartData = BarChartData(dataSet: moodChartDataSet)
            dayMoodChart.data = chartData
            dayMoodChart.chartDescription?.text = ""
            dayMoodChart.rightAxis.enabled = false
            dayMoodChart.xAxis.labelPosition = .bottom
            dayMoodChart.setVisibleYRange(minYRange: 0.0, maxYRange: 6.0, axis: YAxis.AxisDependency.right)
            dayMoodChart.leftAxis.labelCount = 6
            
            dayMoodChart.animate(xAxisDuration: 1.0)
            
            // 水、運動 複合圖(長條加折線)
            let dayWaterExerciseChart = CombinedChartView(frame: CGRect(x: x, y: chartsHeight + 50, width: viewWidth, height: chartsHeight))
            dayWaterExerciseChart.noDataText = "You Need to Provide Your Daily Events."
            
            let waterChartDataSet = BarChartDataSet(values: waterEntries, label: "Water")
            waterChartDataSet.colors = [UIColor.blue]
            waterChartDataSet.drawValuesEnabled = false
            let barChartData = BarChartData(dataSet: waterChartDataSet)
            
            let exerciseChartDataSet = LineChartDataSet(values: exerciseEntries, label: "Exercise")
            exerciseChartDataSet.colors = [UIColor.brown]
            exerciseChartDataSet.drawValuesEnabled = false
            exerciseChartDataSet.circleColors = [UIColor.brown]
            exerciseChartDataSet.circleRadius = 3
            let lineChartData = LineChartData(dataSet: exerciseChartDataSet)
            
            let combinedChartData = CombinedChartData()
            combinedChartData.barData = barChartData
            combinedChartData.lineData = lineChartData
            
            dayWaterExerciseChart.data = combinedChartData
            dayWaterExerciseChart.chartDescription?.text = ""
            dayWaterExerciseChart.rightAxis.enabled = false
            dayWaterExerciseChart.xAxis.labelPosition = .bottom
            dayWaterExerciseChart.setVisibleYRange(minYRange: 0.0, maxYRange: 6.0, axis: YAxis.AxisDependency.right)
            dayWaterExerciseChart.leftAxis.labelCount = 6
            
            dayWaterExerciseChart.animate(xAxisDuration: 1.0)
            
            showView.addSubview(dayMoodChart)
            showView.addSubview(dayWaterExerciseChart)
        } else if chartsType.selectedSegmentIndex == 1 {
            let eventCategoryChart = PieChartView(frame: CGRect(x: x, y: 10, width: viewWidth, height: chartsHeight*2))
            eventCategoryChart.noDataText = "You Need to Provide Your Daily Events."
            if categoryEntries.count > 0 {
                eventCategoryChart.chartDescription?.text = ""

                let chartDataSet = PieChartDataSet(values: categoryEntries, label: "")
                let chartData = PieChartData(dataSet: chartDataSet)
                
                var colors: [UIColor] = []
                for _ in 0..<categoryEntries.count {
                    colors.append(UIColor.init(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 1.0))
                }
                chartDataSet.colors = colors
                
                eventCategoryChart.data = chartData
            }
            showView.addSubview(eventCategoryChart)
        }
    }
    
    @IBAction func cancel(_ sender: UIBarButtonItem) {
        dismiss(animated: true, completion: nil)
    }
}

上一篇
Day 27. Import Charts Library & Develop Chart Page Storyboard
下一篇
Day 29. Talking About Export ipa File & Version vs. Build
系列文
利用Swift 4開發iOS App,Daily Work List31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言