Jump to content

Any Swift nerds? need help pulling data out of datatask()

AlexGoesHigh
Go to solution Solved by aiwiguna,

Your issue is concurrency, you are accessing the weather array in viewDidLoad, numberOfRowsInSection and cellForRowAt function before the datatask finish because the data task is running in background thread.


Here some change that you need to do to fixed it

override func viewDidLoad() {
        super.viewDidLoad()
        getDataFromAPI(){ weather in
            switch weather {
            case .success( let gotWeather):
                self.weather.append(gotWeather)
                print(gotWeather)
                DispatchQueue.main.async {
                    self.latitude.text = String(weather[0].latitude)
                    self.longitude.text = String(weather[0].longitude)

                    self.temperature.text! += String(weather[0].currentWeather.temperature)
                    self.windSpeed.text! += String(weather[0].currentWeather.windspeed)
                    self.windDirection.text! += String(weather[0].currentWeather.windDirection)
                    self.elevation.text! += String(weather[0].elevation)
                    self.table.reloadData()
                }
            case .failure( let error):
                print(error)
            }
        }
        // Do any additional setup after loading the view.
        table.delegate = self
        table.dataSource = self
        }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    	if weather.count != 0 {
        	return weather[0].hourly.time.count
        } else {
        	return 0
        }
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! WeatherTableViewCell
        if weather.count != 0 {
        	cell.loadCell(weather: weather[0], arrayIndex: indexPath.item)
        }
        return cell
    }
}

 

Basically i can't figure out how to get the data of a datatask() call outside the closure, googling and in a lot of tutorials all they do is process the data (aka pass the data to a json decoder when the data is a json request) and assign that to property of the class, and in most cases an empty array of the object obtained from the request, but everytime i do that i always get nil errors of some kind, right now i'm trying to pass the json object into an array but every time i call the array i get index out of bounds, basically is not in the array.

 

i've already tried passing the data to another closure that gets handled when i call the function where i'm dealing with datatask, i've also tried making the request and handling it in a different object and now i don't know what else to try, i guess this forum may be not the best place to ask about programing on apple stuff but i guess i should try, i'd take any help even if just pointing to other places for help.

 

Spoiler

the code:

ViewController:

import UIKit

class ViewController: UIViewController {
    
    private var weather = [Weather]()
    
    @IBOutlet weak var table: UITableView!
    
    @IBOutlet weak var latitude: UILabel!
    @IBOutlet weak var longitude: UILabel!
    
    @IBOutlet weak var temperature: UILabel!
    @IBOutlet weak var windSpeed: UILabel!
    @IBOutlet weak var windDirection: UILabel!
    @IBOutlet weak var elevation: UILabel!
    
    
    //MARK: Class private
    
    private func getDataFromAPI(weather: @escaping (Result<Weather, Error>) -> Void) {
        let weatherURL = "https://api.open-meteo.com/v1/forecast?latitude=40.42&longitude=-3.70&hourly=temperature_2m,relativehumidity_2m,apparent_temperature,precipitation_probability,precipitation,rain,surface_pressure,cloudcover,visibility&current_weather=true&forecast_days=1"
        
        if let url = URL(string: weatherURL) {
            URLSession.shared.dataTask(with: url) { (data, response, error) in
                if let error = error {
                    print(error)
                }
                
                if let response = response {
                    print(response)
                }
                
                guard let data = data else { return }
                weather(Result { try JSONDecoder().decode(Weather.self, from: data)})
            }.resume()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        getDataFromAPI(){ weather in
            switch weather {
            case .success( let gotWeather):
                self.weather[0] = gotWeather
                print(gotWeather)
                DispatchQueue.main.async {
                    self.table.reloadData()
                }
            case .failure( let error):
                print(error)
            }
        }
        // Do any additional setup after loading the view.
        table.delegate = self
        table.dataSource = self

        self.latitude.text = String(weather[0].latitude)
        self.longitude.text = String(weather[0].longitude)

        self.temperature.text! += String(weather[0].currentWeather.temperature)
        self.windSpeed.text! += String(weather[0].currentWeather.windspeed)
        self.windDirection.text! += String(weather[0].currentWeather.windDirection)
        self.elevation.text! += String(weather[0].elevation)
        
        }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return weather[0].hourly.time.count
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! WeatherTableViewCell
        
        cell.loadCell(weather: weather[0], arrayIndex: indexPath.item)
        
        return cell
    }
}

 

if need i can add the json decodable that i've made and the table cell class but i've already checked the data is parsed correctly and the build fails before reaching the table anyways.

this is one of the greatest thing that has happened to me recently, and it happened on this forum, those involved have my eternal gratitude http://linustechtips.com/main/topic/198850-update-alex-got-his-moto-g2-lets-get-a-moto-g-for-alexgoeshigh-unofficial/ :')

i use to have the second best link in the world here, but it died ;_; its a 404 now but it will always be here

 

Link to comment
Share on other sites

Link to post
Share on other sites

27 minutes ago, AlexGoesHigh said:

 

hi alex, hows ur G2?

Dont forget to mark as solution if your question is answered

Note: My advice is amateur help/beginner troubleshooting, someone else can probably troubleshoot way better than me.

- I do have some experience, and I can use google pretty well. - Feel free to quote me I may respond soon.

 

Join team Red, my apprentice

 

STOP SIDING WITH NVIDIA

 

Setup:
Ryzen 7 5800X3DSapphire Nitro+ 7900XTX 24GB / ROG STRIX B550-F Gaming / Cooler Master ML360 Illusion CPU Cooler / EVGA SuperNova 850 G2 / Lian Li Dynamic Evo White Case / 2x16 GB Kingston FURY RAM / 2x 1TB Lexar 710 / iiYama 1440p 165HZ Montitor, iiYama 1080p 75Hz Monitor / Shure MV7 w/ Focusrite Scarlett Solo / GK61 Keyboard / Cooler Master MM712 (daily driver) Logitech G502-X (MMO mouse) / Soundcore Life Q20 w/ Arctis 3 w/ WF-1000XM3

 

CPU OC: -30 all cores @AutoGhz

GPU OC: 3Ghz Core 2750Mhz Memory w/ 25%W increase (460W)

Link to comment
Share on other sites

Link to post
Share on other sites

Your weather array is empty. Initializing it using [Weather]() creates an array with 0 elements. So trying to index into it will crash your app. 

 

instead of `self.weather[0] = gotWeather` (which will give you an index out of range exception), you can use either:

self.weather.insert(gotWeather, at: 0)

// or this

self.weather.append(gotWeather)

 

Link to comment
Share on other sites

Link to post
Share on other sites

Your issue is concurrency, you are accessing the weather array in viewDidLoad, numberOfRowsInSection and cellForRowAt function before the datatask finish because the data task is running in background thread.


Here some change that you need to do to fixed it

override func viewDidLoad() {
        super.viewDidLoad()
        getDataFromAPI(){ weather in
            switch weather {
            case .success( let gotWeather):
                self.weather.append(gotWeather)
                print(gotWeather)
                DispatchQueue.main.async {
                    self.latitude.text = String(weather[0].latitude)
                    self.longitude.text = String(weather[0].longitude)

                    self.temperature.text! += String(weather[0].currentWeather.temperature)
                    self.windSpeed.text! += String(weather[0].currentWeather.windspeed)
                    self.windDirection.text! += String(weather[0].currentWeather.windDirection)
                    self.elevation.text! += String(weather[0].elevation)
                    self.table.reloadData()
                }
            case .failure( let error):
                print(error)
            }
        }
        // Do any additional setup after loading the view.
        table.delegate = self
        table.dataSource = self
        }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    	if weather.count != 0 {
        	return weather[0].hourly.time.count
        } else {
        	return 0
        }
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! WeatherTableViewCell
        if weather.count != 0 {
        	cell.loadCell(weather: weather[0], arrayIndex: indexPath.item)
        }
        return cell
    }
}

 

Link to comment
Share on other sites

Link to post
Share on other sites

5 hours ago, aiwiguna said:

Your issue is concurrency, you are accessing the weather array in viewDidLoad, numberOfRowsInSection and cellForRowAt function before the datatask finish because the data task is running in background thread.


Here some change that you need to do to fixed it

override func viewDidLoad() {
        super.viewDidLoad()
        getDataFromAPI(){ weather in
            switch weather {
            case .success( let gotWeather):
                self.weather.append(gotWeather)
                print(gotWeather)
                DispatchQueue.main.async {
                    self.latitude.text = String(weather[0].latitude)
                    self.longitude.text = String(weather[0].longitude)

                    self.temperature.text! += String(weather[0].currentWeather.temperature)
                    self.windSpeed.text! += String(weather[0].currentWeather.windspeed)
                    self.windDirection.text! += String(weather[0].currentWeather.windDirection)
                    self.elevation.text! += String(weather[0].elevation)
                    self.table.reloadData()
                }
            case .failure( let error):
                print(error)
            }
        }
        // Do any additional setup after loading the view.
        table.delegate = self
        table.dataSource = self
        }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    	if weather.count != 0 {
        	return weather[0].hourly.time.count
        } else {
        	return 0
        }
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! WeatherTableViewCell
        if weather.count != 0 {
        	cell.loadCell(weather: weather[0], arrayIndex: indexPath.item)
        }
        return cell
    }
}

 

THANK YOU!

 

I knew I was dealing with concurrency but i'm new to this so i didn't figured out those conditionals on the table functions for it to do something while its fetching the data, also the labels on dispatch queue.

this is one of the greatest thing that has happened to me recently, and it happened on this forum, those involved have my eternal gratitude http://linustechtips.com/main/topic/198850-update-alex-got-his-moto-g2-lets-get-a-moto-g-for-alexgoeshigh-unofficial/ :')

i use to have the second best link in the world here, but it died ;_; its a 404 now but it will always be here

 

Link to comment
Share on other sites

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×