【发布时间】:2020-03-07 07:29:54
【问题描述】:
我正在使用一些 API 来获取数据。这些是作为 session.dataTask 启动的,我使用类来封装每个不同 API 的 API 调用、方法和返回的属性。当 API 会话结束并且数据可用时,我应该如何配置我的代码以更新相关的屏幕标签和子视图?
AstronomicalTimes 类初始化的相关部分是:
init (date: Date, lat: Float, long: Float) {
let coreURL = "https://api.sunrise-sunset.org/json?"
let position = "lat=\(lat)&lng=\(long)"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let dateString = "&date=" + dateFormatter.string(from: date)
//let dateString = "&date=2020-06-21"
let urlString = coreURL + position + dateString + "&formatted=0"
let session = URLSession.shared
let url = URL(string: urlString)!
let request = URLRequest(url: url)
session.dataTask(with: request, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) in
if let error = error {
let nsError = error as NSError
print("Astronomical Times API call failed with error \(nsError.code)")
return
}
if let response = response as? HTTPURLResponse {
print("Astronomical Times API call response is \(response.statusCode)")
}
if let data = data {
do {
let astronomicalTimesResponse = try JSONDecoder().decode(AstronomicalTimesResponse.self, from: data)
print("Astronomical times successfully parsed")
self.fillFields(astronomicalTimesResponse.results) //completes all class properties from parsed data
} catch {
print("Error while tide details parsing: \(error)")
}
}
}).resume()
在 viewDidLoad() 中为 API 调用的结果分配一个标签:
currentAstronomicalTimes = AstronomicalTimes(date: savedDate, lat: currentSelection.station.lat, long: currentSelection.station.long)
lblAstDawn.text = currentAstronomicalTimes.strings.astronomicalTwilightBegin
很明显,这不起作用,因为在 API 返回数据之前,屏幕呈现为标签和子视图为空白。我不知道如何在 API 完成时向 ViewController 发出信号,然后如何重绘标签等。我尝试更新 API 调用闭包表达式中的 viewController 字段,但我无法从另一个类更新 UILabels (而且我认为这种方法很混乱,因为标签更新逻辑确实应该在 ViewController 中)
任何帮助表示赞赏。
在 Rob 的 cmets 之后更新:
我已按照建议更改了我的类定义,它成功地从 API 加载了数据。类定义如下,注意我添加了一个函数,它获取加载的数据并将其转换为时间字符串和 date() 以便于在 viewController 中使用(这些似乎都在 API 调用后正确填充)
import Foundation
枚举 AstronomicalTimesError: 错误 { case invalidResponse(Data?, URLResponse?) }
类天文时间{
//structures for decoding daylight times
struct AstronomicalTimesResponse: Decodable {
public var results: AstronomicalTimes
public var status: String
}
struct AstronomicalTimes: Decodable {
var sunrise = String()
var sunset = String()
var solarNoon = String()
var dayLength = 0
var civilTwilightBegin = String()
var civilTwilightEnd = String()
var nauticalTwilightBegin = String()
var nauticalTwilightEnd = String()
var astronomicalTwilightBegin = String()
var astronomicalTwilightEnd = String()
private enum CodingKeys : String, CodingKey {
case sunrise = "sunrise"
case sunset = "sunset"
case solarNoon = "solar_noon"
case dayLength = "day_length"
case civilTwilightBegin = "civil_twilight_begin"
case civilTwilightEnd = "civil_twilight_end"
case nauticalTwilightBegin = "nautical_twilight_begin"
case nauticalTwilightEnd = "nautical_twilight_end"
case astronomicalTwilightBegin = "astronomical_twilight_begin"
case astronomicalTwilightEnd = "astronomical_twilight_end"
}
}
//used to hold string values to enter to label, i.e. time strings for labels
var strings = AstronomicalTimes()
//struct and variable used to hold specific date/times for gradient calculation
struct Times {
var sunrise = Date()
var sunset = Date()
var solarNoon = Date()
var dayLength = 0
var civilTwilightBegin = Date()
var civilTwilightEnd = Date()
var nauticalTwilightBegin = Date()
var nauticalTwilightEnd = Date()
var astronomicalTwilightBegin = Date()
var astronomicalTwilightEnd = Date()
}
var times = Times()
let date: Date
let latitude: Float
let longitude: Float
init (date: Date, latitude: Float, longitude: Float) {
self.date = date
self.latitude = latitude
self.longitude = longitude
}
func start(completion: @escaping (Result<AstronomicalTimesResponse, Error>) -> Void) {
var components = URLComponents(string: "https://api.sunrise-sunset.org/json")!
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX") // just in case your end user isn't using Gregorian calendar
dateFormatter.dateFormat = "yyyy-MM-dd"
components.queryItems = [
URLQueryItem(name: "lat", value: "\(latitude)"),
URLQueryItem(name: "lng", value: "\(longitude)"),
URLQueryItem(name: "date", value: dateFormatter.string(from: date)),
URLQueryItem(name: "formatted", value: "0")
]
let session = URLSession.shared
let url = components.url!
let request = URLRequest(url: url)
session.dataTask(with: request) { data, response, error in
if let error = error {
DispatchQueue.main.async {
completion(.failure(error))
}
return
}
guard
let responseData = data,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode
else {
DispatchQueue.main.async {
completion(.failure(AstronomicalTimesError.invalidResponse(data, response)))
}
return
}
do {
print("Astronomical times api completed with status code ", httpResponse.statusCode)
let astronomicalTimesResponse = try JSONDecoder().decode(AstronomicalTimesResponse.self, from: responseData)
DispatchQueue.main.async {
completion(.success(astronomicalTimesResponse))
self.fillFields(astronomicalTimesResponse.results)
}
} catch let jsonError {
DispatchQueue.main.async {
completion(.failure(jsonError))
}
}
}.resume()
}
func fillFields(_ input: AstronomicalTimes) -> Void {
//formats output fields into Date() or String (HH:mm) format
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ" //Your date format
times.sunrise = dateFormatter.date(from: input.sunrise) ?? Date()
times.sunset = dateFormatter.date(from: input.sunset) ?? Date()
times.solarNoon = dateFormatter.date(from: input.solarNoon) ?? Date()
times.dayLength = input.dayLength
times.civilTwilightBegin = dateFormatter.date(from: input.civilTwilightBegin) ?? Date()
times.civilTwilightEnd = dateFormatter.date(from: input.civilTwilightEnd) ?? Date()
times.nauticalTwilightBegin = dateFormatter.date(from: input.nauticalTwilightBegin) ?? Date()
times.nauticalTwilightEnd = dateFormatter.date(from: input.nauticalTwilightEnd) ?? Date()
times.astronomicalTwilightBegin = dateFormatter.date(from: input.astronomicalTwilightBegin) ?? Date()
times.astronomicalTwilightEnd = dateFormatter.date(from: input.astronomicalTwilightEnd) ?? Date()
let timeFormatter = DateFormatter()
timeFormatter.dateFormat = "HH:mm"
strings.sunrise = timeFormatter.string(from: times.sunrise)
strings.sunset = timeFormatter.string(from: times.sunset)
strings.solarNoon = timeFormatter.string(from: times.solarNoon)
strings.dayLength = input.dayLength
strings.civilTwilightBegin = timeFormatter.string(from: times.civilTwilightBegin)
strings.civilTwilightEnd = timeFormatter.string(from: times.civilTwilightEnd)
strings.nauticalTwilightBegin = timeFormatter.string(from: times.nauticalTwilightBegin)
strings.nauticalTwilightEnd = timeFormatter.string(from: times.nauticalTwilightEnd)
strings.astronomicalTwilightBegin = timeFormatter.string(from: times.astronomicalTwilightBegin)
strings.astronomicalTwilightEnd = timeFormatter.string(from: times.astronomicalTwilightEnd)
}
}
然后我从 viewController 中的函数调用它:
func getAstronomicalTimes(date: Date, latitude: Float, longitude: Float) -> Void {
let astronomicalTimes = AstronomicalTimes(date: date, latitude: latitude, longitude: longitude)
astronomicalTimes.start { result in
switch result {
case .success(let astronomicalTimesResponse):
print("astronomical times response ", astronomicalTimesResponse)
print("label", astronomicalTimes.strings.astronomicalTwilightBegin)
self.lblAstDawn.text = astronomicalTimes.strings.astronomicalTwilightBegin
case .failure(let error):
print(error)
}
}
}
这个函数在 viewDidLoad() 中被调用:
getAstronomicalTimes(date: savedDate, latitude: currentSelection.station.lat, longitude: currentSelection.station.long)
但是,getAstronomicalTimes(date:latitude:longitude) 并没有像我希望的那样更新 lblAstDawn.text。
关于我在哪里弄错的任何线索?
【问题讨论】:
-
DispatchQueue.main.async { self.fillFields(astronomicalTimesResponse.results) }
-
谢谢,但这仍然不起作用。 fillFields() 填充类属性。这些属性是在 ViewController 中访问的——我在 viewDidLoad() 中创建了一个类的实例(它获取数据然后运行 fillFields()。该实例有空白作为初始属性值,这些是 viewDidLoad 中返回的值()。属性仅在 API 完成时才完整,因此在分配标签后更新 - 因此在 viewDidLoad() 中填充屏幕标签的代码在 API 返回的数据在实例中可用之前执行。
标签: swift multithreading