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()
}
}
}