希紋的部落格

Swift簡易讀取CSV檔案

讀取外部檔案的方法每個語言都會有,而CSV檔是用固定的格式把純文字文件變得和試算表一樣的表格。跟純文字比起來是能轉化成專案中的Model Class做進一步使用,但又不像JSON已經有一套標準函式庫能夠直接套用。因此需要自己去撰寫。

基本資訊

記得我是截取某個人的回答,在這邊重新整理一遍

製作流程

1.準備來源CSV檔案

雖然這算是廢話,但是有了資料才能夠針對這份資料進行處理,就和處理JSON一樣,要了解文檔格式才能進行解析。

這邊就有一個示意用的CSV內容,在這邊命名為data.csv

Firstname,Last name,Age,Registered
Duncan,Campbell,40,True
Tobi,Dorner,36,False
Saskia,Boogarts,29,True

如果想要自己練習,又不知道該上哪找資料,Mockaroo或許是個好幫手,能產生出一批假資料。

2.以這個CSV檔的內容建立一個Model

基本上就是將欄位項目轉成Model的方式:

struct Person {
    var firstName: String
    var lastName: String
    var age: Int
    var isRegistered: Bool
}

3.宣告這個Model陣列

因為這些結構是單一一筆的資料,要把CSV檔的所有資料都解析出來,就要像這樣:

var people = [Person]()

4.讀取檔案

這邊就是Swift常用的讀取外部資源方法,以範例data.csv就會是這樣:

guard let filepath = Bundle.main.path(forResource: "data", ofType: "csv") else {
    return
}

Bundle.main就默認當下專案APP的主要位置,如果是寫Framework等插件工具的話,需要轉換思路去想要從哪邊取得,否則會因為默認位置而取不到位在Framework的資源檔案。

5.把檔案轉成String

因為CSV本質是純文字檔,用String去接:

var data = ""
do {
    data = try String(contentsOfFile: filepath)
} catch {
    print(error)
    return
}

6.把每一行都分開成單獨的陣列字串

因為我們需要一行一行拆解內容,讓一整包的文字檔才能進行下一步處理:

let rows = data.components(separatedBy: "\n")

7.移除欄位的標題列

標題列主要是給人看,在t程式運作上不需要納入:

rows.removeFirst()

8.用迴圈拆解每列的內容並將內容新增並存入陣列

拆解並新增的完整迴圈大致上長這樣:

for row in rows {
        let columns = row.components(separatedBy: ",")//切出欄位陣列
        //將陣列取出各個欄位
        let firstName = columns[0]
        let lastName = columns[1]
        let age = Int(columns[2]) ?? 0
        let isRegistered = columns[3] == "True"
        //新增Model並把值代入
        let person = Person(firstName: firstName, lastName: lastName, age: age, isRegistered: isRegistered)
        people.append(person)//加入陣列
}

迴圈的每一行就是整個Person的個欄位內容,但是原始文字資料(ex:Duncan,Campbell,40,True)還需要根據欄位做分割成columns才能夠進行,正是.components(separatedBy: ",")這邊的用意。

通常會有兩種做法,其中一種就是將欄位一個個取出,並塞入新增的Model;另一個則是直接在新增Model時就帶入每個欄位。結果都一樣但是閱讀上就有不同的感覺,依照習慣做就好,沒有對錯。

完整程式碼

//MARK: - Model
struct Person {
    var firstName: String
    var lastName: String
    var age: Int
    var isRegistered: Bool
}
//MARK: - value
var people = [Person]()
//MARK: - function
func convertCSVIntoArray() {
    //讀取資源data.csv
    guard let filepath = Bundle.main.path(forResource: "data", ofType: "csv") else {
        return
    }

    //將filepath轉換成String
    var data = ""
    do {
        data = try String(contentsOfFile: filepath)
    } catch {
        print(error)
        return
    }
    
    //分割(split)出每一行(rows)的資料,行分隔常用的是"\n"
    var rows = data.components(separatedBy: "\n")

    //移除標題欄位
    rows.removeFirst()

    //每行資料拆解並加入`people`陣列
    for row in rows {
        let columns = row.components(separatedBy: ",")//列的分隔記號要看原始檔,常用的有`,`、`|`

        //防呆:檢查欄位總數有無超出預期
        //大於預期數量在建立上沒有程式錯誤,但是有可能因為是填錯資料導致這筆資料不可信
        //小於預期則會發生out of range的閃退
        if columns.count == 4 {
            let firstName = columns[0]
            let lastName = columns[1]
            let age = Int(columns[2]) ?? 0
            let isRegistered = columns[3] == "True"

            let person = Person(firstName: firstName, lastName: lastName, age: age, isRegistered: isRegistered)
            people.append(person)
        }
    }
}

總結

這也不是什麼困難的功能,只是久沒用就多少會忘記。

#swift