Обновление полей экрана после завершения фоновой задачи - PullRequest
0 голосов
/ 06 марта 2020

Я использую некоторые API для получения данных. Они инициируются как session.dataTask, и я использую классы для инкапсуляции вызовов API, методов и возвращаемых свойств для каждого отдельного API. Как мне настроить мой код так, чтобы обновлять соответствующие метки экрана и подвиды после завершения сеансов API и получения данных?

Соответствующий раздел init класса 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()

Метке присваивается результат вызова API в viewDidLoad () с:

currentAstronomicalTimes = AstronomicalTimes(date: savedDate, lat: currentSelection.station.lat, long: currentSelection.station.long)
    lblAstDawn.text = currentAstronomicalTimes.strings.astronomicalTwilightBegin

Понятно, что это не работает, поскольку экран отображается с пустыми надписями и метками до того, как API вернет данные. Я не могу понять, как сигнализировать ViewController о завершении API, а затем как перерисовать метки и т. Д. c. Я попытался обновить поля viewController в выражении закрытия вызовов API, но я не могу обновить UILabels из другого класса (и я думаю, что этот подход является грязным, так как logi update logi c действительно должен быть в ViewController)

Любая помощь приветствуется.

ОБНОВЛЕНИЕ после комментариев Роба:

Я изменил определение класса в соответствии с рекомендациями и успешно загружает данные из API. Определение класса приведено ниже: обратите внимание, я добавляю функцию, которая берет загруженные данные и превращает их в строки времени и date () для простоты использования в viewController (все они, по-видимому, правильно заполнены после вызова API)

import Foundation

enum AstronomicalTimesError: Ошибка {case invalidResponse (Data ?, URLResponse?)}

class AstronomicalTimes {

//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 как я и надеялся.

Какие-нибудь подсказки относительно того, где я это неправильно понял?

1 Ответ

0 голосов
/ 06 марта 2020

Вам необходимо предоставить обработчик завершения для вашего запроса AstronomicalTimes, чтобы он мог сообщить контроллеру представления, когда были получены данные, и контроллер представления затем может обновить различные поля.

Таким образом:

enum AstronomicalTimesError: Error {
    case invalidResponse(Data?, URLResponse?)
}

class AstronomicalTimes {
    let date: Date
    let latitude: Float // generally we use Double, but for your purposes, this might be adequate
    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 {
                let astronomicalTimesResponse = try JSONDecoder().decode(AstronomicalTimesResponse.self, from: responseData)
                DispatchQueue.main.async {
                    completion(.success(astronomicalTimesResponse))
                }
            } catch let jsonError {
                DispatchQueue.main.async {
                    completion(.failure(jsonError))
                }
            }
        }.resume()
    }
}

Тогда ваш viewDidLoad может сделать что-то вроде:

override viewDidLoad() {
    super.viewDidLoad()

    let astronomicalTimes = AstronomicalTimes(date: someDate, latitude: someLatitude, longitude: someLongitude)

    astronomicalTimes.start { result in
        switch result {
        case .success(let astronomicalTimesResponse):
            // populate your fields here

        case .failure(let error):
            print(error)
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...