The Util Designer
SwiftUI 使用URLSession的URLSessionDataTask下載JSON Data
xcode 13.4.1, swift 5.5, iOS 15.4
2022-09-08
現在絕大部分App都不會獨立的,一定會與後台或其他的服務器互相溝通,最簡單是從服務器獲取資料然後顯示出來。這次我們試試從 https://reqres.in/從獲取一個假的人員列表,並使用List顯示出來。
1. 我們使用https://reqres.in/作為後台,獲取一個假的人員列表,其JSON數據結構如下,這次我們只使用data的部分,data包含一系列的user內容,每個user有id, email,first_name,last_name和avatar(相片):
{
    "page":2,
    "per_page":6,
    "total":12,
    "total_pages":2,
    "data":[
        {
            "id":7,
            "email":"[email protected]",
            "first_name":"Michael",
            "last_name": "Lawson",
            "avatar":"https://reqres.in/img/faces/7-image.jpg"
        },
        ...
    ],
    "support":
        {
            "url":"https://reqres.in/#support-heading",
            "text":"To keep ReqRes free, contributions towards server costs are appreciated!"
        }
}
2. 簡單的下載數據可以使用URLSession,在使用URLSession之前,需要指定的設置URLSessionConfiguration,不過現在只要使用URLSessionConfiguration的預設值就可以,由於這里我們使用URLSession.dataTask方法,只要指定需要下載的URL就可以,實作如下:
struct URLSessionDataTaskExample: View {

    var body: some View {
        VStack {
            Text("Hello")
        }
            .onAppear {
                let configuration = URLSessionConfiguration.default
                let session = URLSession(configuration: configuration) // (1) initial session with URLSessionConfiguration.default
                
                // (2) specific url of data
                guard let url = URL(string: "https://reqres.in/api/users?page=2") else {
                    return
                }
                
                let task = session.dataTask(with: url) { data, response, error in //(3) specific url to download
                    guard let httpResponse = response as? HTTPURLResponse, //(4) retrieve HTTPURLResponse
                          (200..<300).contains(httpResponse.statusCode) else { //(5) check the return http status
                        return
                    }
                    guard let data = data else {
                        return
                    }
                    
                    if let jsonData = String(data: data, encoding: .utf8) { //(6) convert the return data to string type
                        print(jsonData)
                    }
                }
                task.resume()//(7)
            }
    }
}
程式解釋:
(1) 初始化URLSession
(2)初始化URL
(3)使用session的dataTask創建下載的任務
(4)下載數據後的處理程式可以寫到dataTask所提供的closure中,先取得HTTPURLResponse
(5)檢查返回的http狀態是否是成功的狀態
(6)取得下載的數據並轉成為字符串類型,再打印到console中
(7)使用session的dataTask創建下載任務後,需要調用任務的resume方法才會實際運行
運行後,在Xcode的console應看到下載的JSON內容如下圖所示:
3. 下載後,可以使用 Swift 利用JSONDecoder來轉換成Object 的方式把string轉成Swift所能使用的objects,首先先訂議與JSON結構一樣的struct,如下所示:
struct UserList : Codable {
    let page : Int
    let per_page : Int
    let total : Int
    let total_pages : Int
    let data : [User]
}

struct User : Codable {
    let id : Int
    let email : String
    let first_name : String
    let last_name : String
    let avatar : String
}
4. 再使用JSONDecoder把string轉成struct:
struct URLSessionDataTaskExample: View {

    var body: some View {
        VStack {
            Text("Hello")
        }
            .onAppear {
                let configuration = URLSessionConfiguration.default
                let session = URLSession(configuration: configuration) // (1) initial session with URLSessionConfiguration.default
                
                // (2) specific url of data
                guard let url = URL(string: "https://reqres.in/api/users?page=2") else {
                    return
                }
                
                let task = session.dataTask(with: url) { data, response, error in //(3) specific url to download
                    guard let httpResponse = response as? HTTPURLResponse, //(4) retrieve HTTPURLResponse
                          (200..<300).contains(httpResponse.statusCode) else { //(5) check the return http status
                        return
                    }
                    guard let data = data else {
                        return
                    }
                    
                    if let jsonData = String(data: data, encoding: .utf8) { //(6) convert the return data to string type
                        do {
                            let decoder = JSONDecoder()
                            let userList = try decoder.decode(UserList.self, from: Data(jsonData.utf8))
                            print(userList)
                        } catch let error {
                            print(error)
                        }
                    }
                }
                task.resume()
            }
    }
}
運行完程式之後,應可以看到在console有打印struct的內容:
4. 最後使用List把得到的User Array展示出來:
struct URLSessionDataTaskExample: View {
    @State var userList : UserList?
    var body: some View {
        VStack {
            if let userList = self.userList {
                List {
                    ForEach(0..<userList.data.count, id: \.self) { i in
                        HStack {
                            Text(userList.data[i].first_name)
                            Text(userList.data[i].last_name)
                            Text(userList.data[i].email)
                            AsyncImage(url: URL(string: userList.data[i].avatar))
                        }
                    }
                }
            }
        }
            .onAppear {
                let configuration = URLSessionConfiguration.default
                let session = URLSession(configuration: configuration) // (1) initial session with URLSessionConfiguration.default
                
                // (2) specific url of data
                guard let url = URL(string: "https://reqres.in/api/users?page=2") else {
                    return
                }
                
                let task = session.dataTask(with: url) { data, response, error in //(3) specific url to download
                    guard let httpResponse = response as? HTTPURLResponse, //(4) retrieve HTTPURLResponse
                          (200..<300).contains(httpResponse.statusCode) else { //(5) check the return http status
                        return
                    }
                    guard let data = data else {
                        return
                    }
                    
                    if let jsonData = String(data: data, encoding: .utf8) { //(6) convert the return data to string type
                        do {
                            let decoder = JSONDecoder()
                            let userList = try decoder.decode(UserList.self, from: Data(jsonData.utf8))
                            self.userList = userList
                        } catch let error {
                            print(error)
                        }
                    }
                }
                task.resume()
            }
    }
}
5. 還可以做一點顯示的調整就可以有很好的效果了:

struct URLSessionDataTaskExample: View {
    @State var userList : UserList?
    var body: some View {
        VStack {
            if let userList = self.userList {
                List {
                    ForEach(0..<userList.data.count, id: \.self) { i in
                        HStack {
                            AsyncImage(
                                url: URL(string:userList.data[i].avatar),
                                content: { image in
                                    image.resizable()
                                         .aspectRatio(contentMode: .fit)
                                         .frame(width: 80)
                                },
                                placeholder: {
                                    ProgressView()
                                }
                            )
                            VStack {
                                HStack() {
                                    Text(userList.data[i].first_name)
                                    Text(userList.data[i].last_name)
                                }
                                Label(userList.data[i].email, systemImage: "envelope")
                            }
                            .font(.system(size: 10))
                        }
                    }
                }
            }
        }
            .onAppear {
                let configuration = URLSessionConfiguration.default
                let session = URLSession(configuration: configuration) // (1) initial session with URLSessionConfiguration.default
                
                // (2) specific url of data
                guard let url = URL(string: "https://reqres.in/api/users?page=2") else {
                    return
                }
                
                let task = session.dataTask(with: url) { data, response, error in //(3) specific url to download
                    guard let httpResponse = response as? HTTPURLResponse, //(4) retrieve HTTPURLResponse
                          (200..<300).contains(httpResponse.statusCode) else { //(5) check the return http status
                        return
                    }
                    guard let data = data else {
                        return
                    }
                    
                    if let jsonData = String(data: data, encoding: .utf8) { //(6) convert the return data to string type
                        do {
                            let decoder = JSONDecoder()
                            let userList = try decoder.decode(UserList.self, from: Data(jsonData.utf8))
                            self.userList = userList
                        } catch let error {
                            print(error)
                        }
                    }
                }
                task.resume()
            }
    }
}