I have an API of list of countries and I'm trying to get access to the data fetched from the API outside its function.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var apiData:[Countries] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.tableView.delegate = self
self.tableView.dataSource = self
fetchAPI()
}
func fetchAPI() {
let apiEndPoint = "https://restcountries.com/v2/all"
guard let url = URL(string: apiEndPoint) else {
print("Could not convert API endpoint to url object")
return
}
URLSession.shared.dataTask(with: url) { (data,response, error) in
if let err = error {
print("Error occured while fetching the data")
print(err)
return
}
if let jsonData = data {
do {
let decoder = JSONDecoder()
let decodedItem:[Countries] = try decoder.decode([Countries].self, from: jsonData)
DispatchQueue.main.async {
self.apiData = decodedItem
}
} catch let error {
print("An error occured during JSON decoding")
print(error)
}
}
}.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as UITableViewCell
return cell
}
}
I tried defining an apiData variable outside the scope and passing the data to it but when I try to use it in my tableView it is empty.
What am I doing wrong?
Assuming that no error occurs in your fetchAPI function, after getting response and passing the data to your apiData variable, you should reload tableView data:
self.tableView.reloadData()
And in numberOfRowsInSection function:
return apiData.count
Then you should configure each cell to show your data inside cellForRowAt function.
Related
I am using an endpoint that returns a JSON as response. The problem is response json is a huge data to process for me. From that I want to show all the Surah(englishName) name to display in the tableview. I tried my best as a new bee to iOS development. Please take a look at my snippet and let me know where i am doing wrong.
my Json data here:
ViewController code:
var surahName = [Surah]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
//MARK: JSON parse
func parseJSON() {
let url = URL(string: "https://api.alquran.cloud/v1/quran/ar.alafasy")
guard url != nil else{
print("URL Founr Nill")
return
}
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil && data != nil{
do{
self.surahName = try JSONDecoder().decode([Surah].self, from: data!)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}catch{
print(error)
}
}
}.resume()
}
//MARK: Tableview delegate
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return surahName.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:QuranAudioCell = tableView.dequeueReusableCell(withIdentifier: "cell") as! QuranAudioCell
let arrdata = surahName[indexPath.section].data.surahs
cell.nameLbl.text = arrdata[indexPath.row].englishName
return cell
}
Problem is its not printing anything in the tablview.
Change first line as
var surahNames = [EnglishName]()
Inside do-catch block change
self.surahName = try JSONDecoder().decode([Surah].self, from: data!)
into
let response = try JSONDecoder().decode(Surah.self, from: data!)
self.surahName = response.data.surahs
Now inside cellForRowAtIndexPath do this
let surah = surahName[indexPath.row]
cell.nameLbl.text = surah.englishName
I can't figure out why the cells don't return with data.
I can parse normally using the Decodable, which means that is working.
I've been trying all the methods I find without success.
struct MuscleGroup: Decodable {
let ExcerciseID: String
let description: String
let excerciseName: String
let muscleGroup: String
}
class ExerciseListViewController: UITableViewController {
var muscleGroup = [MuscleGroup]()
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return muscleGroup.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ExerciseList", for: indexPath) as! ExcerciseList
let muscle = muscleGroup[indexPath.row]
cell.textLabel!.text = muscle.excerciseName
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(self.muscleGroup[indexPath.row])
tableView.deselectRow(at: indexPath, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.dataSource = self
self.tableView.delegate = self
getJson()
}
func getJson(){
guard let url = URL(string: "https://jsonurl") else { return }
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
print(data)
do {
let muscles = try JSONDecoder().decode([MuscleGroup].self, from: data)
for muscle in muscles {
let muscleGroup = muscle.excerciseName
print(muscleGroup)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
} catch {
print(error)
}
}
}.resume()
}
If I change the var muscleGroup = String to ["Chest", "Back", "Abdominals","Arms", "Legs"] it returns correctly.
Also, the print result on the console returns all the data that needs to be on the Table View.
What am I doing wrong?
As you probably want to use the entire struct
Replace
var muscleGroup = [String]()
with
var muscleGroups = [MuscleGroup]()
Replace
let muscles = try JSONDecoder().decode([MuscleGroup].self, from: data)
for muscle in muscles {
let muscleGroup = muscle.excerciseName
print(muscleGroup)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
with
self.muscleGroups = try JSONDecoder().decode([MuscleGroup].self, from: data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
Replace
cell.textLabel?.text = self.muscleGroup[indexPath.row]
with
cell.textLabel?.text = self.muscleGroups[indexPath.row].muscleGroup
In your getJson function this line
let muscleGroup = muscle.excerciseName
is creating a new local variable called muscleGroup, change the line to be
self.muscleGroup.append(muscle.excerciseName)
i.e. get rid of the let and append the value to the main array variable
Also move the
DispatchQueue.main.async {
self.tableView.reloadData()
}
to be outside of the for loop of muscles as you are forcing the table to reload for each entry rather than when you are finished
var pokemons = [Pokemon]()
func loadJSON() {
let url = "https://pokeapi.co/api/v2/pokemon/"
guard let urlObj = URL(string: url) else { return }
URLSession.shared.dataTask(with: urlObj) {(data, response, error) in
guard let data = data else { return }
do {
let pokedex = try JSONDecoder().decode(Pokedex.self, from: data)
for pokemon in pokedex.results {
guard let jsonURL = pokemon.url else { return }
guard let newURL = URL(string: jsonURL) else { return }
URLSession.shared.dataTask(with: newURL) {(data, response, error) in
guard let data = data else { return }
do {
let load = try JSONDecoder().decode(Pokemon.self, from: data)
self.pokemons.append(load)
} catch let jsonErr {
print("Error serializing inner JSON:", jsonErr)
}
}.resume()
}
} catch let jsonErr{
print("Error serializing JSON: ", jsonErr)
}
}.resume()
}
This code compiles and runs fine, but the array pokemons returns a count of 0 when I check its size afterwards. It's strange because if I loop through the array right after the pokemons.append(load), it'll spit out a bunch of data. It's as if the data gets lost as soon as it's out of scope of the JSON. Does my array need to be passed by reference or something? I'm looking to take the data and put it into a UITableView, but so far nothing is showing.
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Pokemon"
navigationController?.navigationBar.prefersLargeTitles = true
loadJSON()
print(pokemons.count) //Prints 0
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let label = UILabel()
label.text = "By Id"
label.backgroundColor = UIColor.lightGray
return label
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return pokemons.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
let name = pokemons[indexPath.row].name
cell.textLabel?.text = "\(name) Section:\(indexPath.section) Row:\(indexPath.row)"
return cell
}
You either need
self.pokemons.append(load)
DispatchQueue.main.async {
self.tableView.reloadData()
}
or write a completion
the asynchronous call is something that doesn't go in serial with function written code , but runs asynchronously until it finishes then it notifies it's callback and in your code the asynchronous method is URLSession.shared.dataTask which runs in a background thread (that's why you have to insert DispatchQueue.main.async inside it to refresh the UI ) and leave the serial execution at main thread for sure you can block the main thread until response returns with a method like Data(contentsOf:url) instead of URLSession.shared.dataTask but it's not a good idea
I am trying to get an API call to populate the cells of a list view but cannot seem to get the data to be gathered before the cells are generated. I have tried making a completion handler as like here to no avail as well as a semaphore from the same link. While the completion handler compiled, it still did not get the data in time when executing (admittedly it was my first attempt at a completion handler). Here is the original code w/o the completion handler:
override func viewDidLoad() {
super.viewDidLoad()
RestaurantListView.delegate = self
RestaurantListView.dataSource = self
//API setup
guard let url = URL(string: "https://myURL.com") else {
print("ERROR: Invalid URL")
return
}
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) -> Void in
// URL request is complete
guard let data = data else {
print("ERROR: Unable to access content")
return
}
guard let blog = try? JSONDecoder().decode(Blog.self, from: data) else {
print("Error: Couldn't decode data into Blog")
return
}
self.myData = blog
}
task.resume()
}
The table and cells are initialized below these functions:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = RestaurantListView.dequeueReusableCell(withIdentifier: "cell")
cell?.textLabel?.text = myAPIValue
return cell!
}
I have several print statements to track the program and it goes through viewDidLoad then the first tableView which sets up the table then the second which handles the cells, finally it goes through the URL task.
I have rewritten this dozens of different ways and have no idea how to make this execute in the correct order.
You need to reload the table , as this line URLSession.shared.dataTask(with: url) triggers an asynchronous task that isn't in the same sequential order of your lines of code
guard let blog = try? JSONDecoder().decode(Blog.self, from: data) else {
print("Error: Couldn't decode data into Blog")
return
}
self.myData = blog
DispatchQueue.main.async{
RestaurantListView.reloadData()
}
You have to save your data in a variable as you can see as follow, I used arrayOfData. Look the reloadData() function which is called after saving the data.
func downloadData (){
let url = URL(string: "http://www.url.com")
if let url = url {
let task = URLSession.shared.dataTask(with: url, completionHandler: {(data, response, error) in
do {
if let dataResponse = data{
let responseDecoded = try JSONDecoder().decode(Response.self, from: dataResponse)
DispatchQueue.main.async {
self.arrayOfData = responseDecoded.results ?? []
self.RestaurantListView.reloadData()
}
}
}
catch {
print("Error: \(error)")
}
})
task.resume()
}
}
You have to conform the protocol with that data
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
/**If you have no elements you should return 0**/
return arrayOfData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = RestaurantListView.dequeueReusableCell(withIdentifier: "cell"){
cell.textLabel?.text = arrayOfData[indexPath.row].name
return cell
}
return UITableViewCell()
}
I am trying to load the UItable with some data from the external database. I am able to print the required data in the output panel but cant get the data on the tableview. How can I fix this?
Code:
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
//let myarray = ["item1", "item2", "item3"]
var group = [Group]()
#IBOutlet weak var tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "http://www.myurl/myfile.php")
let task = URLSession.shared.dataTask(with: url! as URL) { data, response, error in
guard let data = data, error == nil else { return }
print(NSString(data: data, encoding: String.Encoding.utf8.rawValue))
}
task.resume()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tableview.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//return myarray.count
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "groupCell", for: indexPath) as! UITableViewCell
// cell.textLabel?.text = myarray[indexPath.item]
return cell
}
}
Tableview reload before receiving data. You need to reload table view after completing task.
let task = URLSession.shared.dataTask(with: url! as URL) { data, response, error in
guard let data = data, error == nil else { return }
print(NSString(data: data, encoding: String.Encoding.utf8.rawValue))
//Store into your array depend on response.
DispatchQueue.main.async() {
self.tableView.reloadData()
} } task.resume()