Populating UIComponents With JSON Data - json

I gotta parse and discover an elegant way to work with my parsed data and also use it to populate correctly UIElements, such as, UITableViews, UICollectionViews and etc. I'll post below how I'm parsing and also the JSON file.
import Foundation
struct Contents : Decodable {
let data : [Content]
}
struct Content : Decodable {
let id : Int
let descricao : String
let urlImagem : String
}
API Response file:
import Foundation
var audioBook = [Content]()
func getAudiobooksAPI() {
let url = URL(string: "https://alodjinha.herokuapp.com/categoria")
let session = URLSession.shared
guard let unwrappedURL = url else { print("Error unwrapping URL"); return }
let dataTask = session.dataTask(with: unwrappedURL) { (data, response, error) in
guard let unwrappedDAta = data else { print("Error unwrapping data"); return }
do {
let posts2 = try JSONDecoder().decode(Contents.self, from: unwrappedDAta)
audioBook = posts2.data
} catch {
print("Could not get API data. \(error), \(error.localizedDescription)")
}
}
dataTask.resume()
}
TableView file:
How can I populate using the data I parsed? I really have difficult on doing that. I need an explanation to guide me on how to do it using an elegant way and also as a Senior Developer.
import UIKit
class ViewController: UIViewController, UICollectionViewDataSource {
#IBOutlet var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
getAudiobooksAPI()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return ?????
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as! CollectionViewCell
???????
return cell
}
}
JSON:
JSON Model

Just update your API Service with completion Handler
func getAudiobooksAPI(_ completion:#escaping ([Content])->Void) {
let url = URL(string: "https://alodjinha.herokuapp.com/categoria")
let session = URLSession.shared
guard let unwrappedDAta = data else { completion([Content]()); return}
let dataTask = session.dataTask(with: unwrappedURL) { (data, response, error) in
guard let unwrappedDAta = data else { print("Error unwrapping data"); return }
do {
let posts2 = try JSONDecoder().decode(Contents.self, from: unwrappedDAta)
completion(posts2.data)
} catch {
print("Could not get API data. \(error), \(error.localizedDescription)")
completion([Content]())
}
}
dataTask.resume()
}
And Use Like that
import UIKit
class ViewController: UIViewController, UICollectionViewDataSource {
#IBOutlet var collectionView: UICollectionView!
var dataSource: [Content] = [Content]()
override func viewDidLoad() {
super.viewDidLoad()
getAudiobooksAPI { (data) in
DispatchQueue.main.async {
self.dataSource = data
self.collectionView.reloadData()
}
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.dataSource.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as! CollectionViewCell
let content = self.dataSource[indexPath.item]
return cell
}
}

Related

How to get data from an API using Swift

I'm trying to get data from an API into my table view, but app goes into the catch error " json error". I'll share the code with you.
class ViewController: UIViewController {
#IBOutlet weak var homeTableView: UITableView!
var repository = [RepositoryStats]()
override func viewDidLoad() {
super.viewDidLoad()
downloadJSON {
self.homeTableView.reloadData()
}
homeTableView.delegate = self
homeTableView.dataSource = self
}
func downloadJSON (completed: #escaping () -> ()) {
let url = URL (string: "https://api.github.com/search/repositories?q=language:Swift+language:RXSwift&sort=stars&order=desc")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil {
do {
self.repository = try JSONDecoder().decode([RepositoryStats].self, from: data!)
DispatchQueue.main.async {
completed()
}
}catch {
print ("json error")
}
}
}.resume()
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return repository.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.textLabel?.text = repository[indexPath.row].full_name
return cell
}
This is my struct where I declared :
struct RepositoryStats: Decodable {
let items: [Item]
}
struct Item: Decodable {
let fullName: String
}
If anyone know, why I am getting into the "json error" catch ? Thank you !
Link : https://api.github.com/search/repositories?q=language:Swift+language:RXSwift&sort=stars&order=desc
I fixed it. I have changed my struct into this :
struct RepositoryStats: Decodable {
let items: [Item]
}
struct Item: Decodable {
let full_name: String
let forks_count: Int
let stargazers_count: Int
}
And this is my downloaad JSON func.
func downloadJSON (completed: #escaping () -> ()) {
let url = URL (string: "https://api.github.com/search/repositories?q=language:Swift+language:RXSwift&sort=stars&order=desc")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil {
do {
self.repository = try JSONDecoder().decode(RepositoryStats.self, from: data!)
DispatchQueue.main.async {
completed()
}
}catch {
print (error)
}
}
}.resume()
}
}

How to return an array of objects using SearchBar and for loop

I am wanting the search bar to return the title for each object in the Array. I believe the problem is in my for loop but I am not completed sure. Hopefully, you guys are able to tell me what I am doing wrong.
I am searching through an API. This is the array I am attempting to search through
struct ProductResponse: Decodable {
let results: [SearchResults]
}
struct SearchResults: Decodable {
let title: String
let id:Int
}
I created a for loop to run through each object in the array and get the title and id.
func fetchProduct(productName: String) {
let urlString = "\(searchURL)&query=\(productName)&number=25"
performRequest(urlString: urlString)
}
func performRequest(urlString: String) {
// Create a URL
if let url = URL(string: urlString) {
//Create a URLSession
let session = URLSession(configuration: .default)
// Give the session a task
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
self.parseJSON(productTitle: safeData)
}
}
// Start the task
task.resume()
}
}
func parseJSON(productTitle: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(ProductResponse.self, from: productTitle)
print(decodedData.results)
} catch {
print(error)
}
}
}
I created this function to reload the data in my tableview. When I search for the product results in the Search Bar, my tableview doesn't return anything. The goal is to have my tableview return each result in a tableview cell
var listOfProducts = [SearchResults]() {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData()
self.navigationItem.title = "\(self.listOfProducts.count) Products found"
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
productSearch.delegate = self
}
func downloadJSON() {
guard let downloadURL = url else { fatalError("Failed to get URL")}
URLSession.shared.dataTask(with: downloadURL) { (data, Response, error) in
print("downloaded")
}.resume()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return listOfProducts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let product = listOfProducts[indexPath.row].title
cell.textLabel?.text = product
return cell
}
}
extension ProductsTableViewController: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if let searchBarText = searchBar.text {
searchRequest.fetchProduct(productName: searchBarText)
}
}
}
This is the result
enter image description here
If everything goes well and You got the data under "decodedData.results" of "parseJSON" method, And I saw "decodedData.results" and "listOfProducts" both variables are the same type of SearchResults. So you can just add the one line of under "parseJSON" method as follows:-
func parseJSON(productTitle: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(ProductResponse.self, from: productTitle)
print(decodedData.results)// it have some array data of type SearchResults
self.listOfProducts = decodedData.results
self.tableView.reloadData()
} catch {
print(error)
}
}

Can't get data from rest API to load on ViewDidLoad

I'm new to swift and I can't get my head around this issue.
I can successfully make JSON fetch from an API, but data on the Table View only load after I click a button. Calling the same function on the viewDidLoad didn't load the data when the app is opening.
I tried lot of solutions, but I can't find where the fault is
here's the main view controller code:
import UIKit
struct ExpenseItem: Decodable {
let name: String
let amount: String
let id: String
let timestamp: String
let description: String
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBAction func fetchData(_ sender: Any) {
ds()
self.eTW.reloadData()
}
#IBOutlet weak var eTW: UITableView!
var allExpenses = [ExpenseItem]()
let expensesURL = "https://temp.cuttons.com/json/expenses.json"
override func viewDidLoad() {
super.viewDidLoad()
eTW.delegate = self
eTW.dataSource = self
ds()
self.eTW.reloadData()
}
func parseJSON(data: Data) -> [ExpenseItem] {
var e = [ExpenseItem]()
let decoder = JSONDecoder()
do {
e = try decoder.decode([ExpenseItem].self, from: data)
} catch {
print(error.localizedDescription)
}
return e
}
func ds() {
if let url = URL(string: expensesURL){
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, session, error) in
if error != nil {
print("some error happened")
} else {
if let content = data {
self.allExpenses = self.parseJSON(data: content)
}
}
}
task.resume()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.allExpenses.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = eTW.dequeueReusableCell(withIdentifier: "expense", for: indexPath) as! myCellTableViewCell
cell.name.text = self.allExpenses[indexPath.row].name
if let am = Double(self.allExpenses[indexPath.row].amount) {
if (am > 0) {
cell.amount.textColor = .green
} else {
cell.amount.textColor = .red
}
}
cell.amount.text = self.allExpenses[indexPath.row].amount
return cell
}
thanks
L.
You have to reload the table view after receiving the data asynchronously.
Remove the line in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
eTW.delegate = self
eTW.dataSource = self
ds()
}
and add it in ds
func ds() {
if let url = URL(string: expensesURL) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if let error = error {
print("some error happened", error)
} else {
self.allExpenses = self.parseJSON(data: data!)
DispatchQueue.main.async {
self.eTW.reloadData()
}
}
}
task.resume()
}
}
Do not reload data after the api call in viewDidLoad(). Do it inside of the completion block after you parse the JSON into the object you need (assuming it's successful like you said).
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, session, error) in
guard error == nil else {
print("some error happened")
return
}
guard let data = data else {
print("bad JSON")
return
}
self.allExpenses = self.parseJSON(data: data)
DispatchQueue.main.async {
self.eTW.reloadData()
}
}
task.resume()
Also, use guard let instead of if let if possible.

How to parse Json from URL and display in Collection View in Xcode

Here is a sample of my Api code that I am trying to parse
{"#iot.nextLink" : "Google.com","value" : [ {
"phenomenonTime" : "2020-02-12T01:38:17.622Z",
"resultTime" : "2020-02-12T01:38:17.622Z",
"result" : 1009.3,
"Datastream#1" : "Yahoo.com",
"FeatureOfInterest#2" : "bing.com",
"#iot.id" : 4093015,
"#iot.selfLink" : "youtube.com"},
{"phenomenonTime" : "2020-02-12T01:23:11.397Z",
"resultTime" : "2020-02-12T01:23:11.397Z",
"result" : 1009.7,
"Datastream#1" : "walmart.com",
"FeatureOfInterest#2" : "bestbuy.com",
"#iot.id" : 4092867,
"#iot.selfLink" : "gmail.com"}, ...]}
I have created structures to parse the Json
struct Initial: Decodable {
let value : [Blue]}
struct Blue: Decodable {
let phenomenonTime : String
let result : Double}
I only want to display the PhenomenonTime value and the result value. Next, I did
var url1 = ""
override func viewDidLoad() {
let url = URL(string: url1)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
guard let data = data else { return }
do {
let initial = try? JSONDecoder().decode(Initial.self, from: data)
print(initial?.value)
}catch {
print("Error")
}
}.resume()
}
Here is my code of me parsing the JSON. I can print the values
Optional([GTSeaLevelAPP.Blue(phenomenonTime: "2020-02-12T01:38:17.622Z", result: 1009.3),
GTSeaLevelAPP.Blue(phenomenonTime: "2020-02-12T01:23:11.397Z", result: 1009.7),...])
But, it doesn't allow me when I try to do
print(initial?.value.result)
An error comes up saying "Value of type '[Blue]' has no member 'result'". So, I don't know how to fix this issue of how to print just values or just phenomenonTime so I can put just put values in the collection view or just put phenomenonTime in another collection view. Also, I didn't know how to parse the Json so the collection view can see the array of just phenomenonTime.
extension ViewController:UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = pressureView.dequeueReusableCell(withReuseIdentifier: "check", for: indexPath) as? PressureCollectionViewCell
cell?.number.text=String(indexPath.row)
return cell!
}
Currently, I made it so it will just present a different number in each cell but I don't know how to get all the values into one array and all the phenomenonTime into one array and present them into the collectionview. When I try to refer to make the cell text display the values
cell?.number.text=initial?.value[indexPath.row]
It says:
"Use of unresolved identifier 'initial'"
So, what to do to fix it?
So, set your viewcontroller like:
class ViewController: UIViewController {
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.delegate = self
cv.dataSource = self
cv.backgroundColor = .lightGray
cv.translatesAutoresizingMaskIntoConstraints = false
return cv
}()
var url1 = "your_url"
private var cellId: String = "12312"
private var phenomenonTimeArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.collectionView)
NSLayoutConstraint.activate([
self.collectionView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
self.collectionView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
self.collectionView.topAnchor.constraint(equalTo: self.view.topAnchor),
self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
])
self.collectionView.register(PressureCollectionViewCell.self, forCellWithReuseIdentifier: self.cellId)
self.getDataFromServer()
}
func getDataFromServer() {
let url = URL(string: url1)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
guard let data = data else { return }
do {
let initial = try? JSONDecoder().decode(Initial.self, from: data)
if let value = initial?.value {
self.phenomenonTimeArray = value.map { (blue) -> String in
return blue.phenomenonTime
}
self.collectionView.reloadData()
}
} catch {
print("Error")
}
}.resume()
}
}
extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.phenomenonTimeArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.cellId, for: indexPath) as! PressureCollectionViewCell
cell.item = phenomenonTimeArray[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return .init(width: collectionView.frame.width, height: 30)
}
}
And your cell(design your cell as per requirement):
class PressureCollectionViewCell: UICollectionViewCell {
var item: String? {
didSet {
self.label.text = item
}
}
private lazy var label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.setupViews()
}
required init?(coder: NSCoder) {
fatalError()
}
private func setupViews() {
self.addSubview(self.label)
NSLayoutConstraint.activate([
self.label.leadingAnchor.constraint(equalTo: self.leadingAnchor),
self.label.trailingAnchor.constraint(equalTo: self.trailingAnchor),
self.label.topAnchor.constraint(equalTo: self.topAnchor),
self.label.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}
}

Hi there , I'm new to iOS development and I'm having a hard time populating a tableview embedded in a UIviewcontroller with json data

I'm new to iOS development and I'm having a hard time populating a tableview embedded in a UIviewcontroller with json data.
''import UIKit
class
FirstViewController:UIViewController,UITableViewDataSource,UITableViewDelegate{
#IBOutlet weak var tableview: UITableView!
var TableData:Array< String > = Array < String >()
var valueToPass:String!
override func viewDidLoad() {
super.viewDidLoad()
get_data_from_url("http://www.kaleidosblog.com/tutorial/tutorial.json")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return TableData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FirstViewCell", for: indexPath)
cell.textLabel?.text = TableData[indexPath.row]
return cell
}
func get_data_from_url(_ link:String)
{
let url:URL = URL(string: link)!
let session = URLSession.shared
let request = NSMutableURLRequest(url: url)
request.httpMethod = "GET"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
let task = session.dataTask(with: request as URLRequest, completionHandler: {
(
data, response, error) in
guard let _:Data = data, let _:URLResponse = response , error == nil else {
return
}
self.extract_json(data!)
})
task.resume()
}
func extract_json(_ data: Data)
{
let json: Any?
do
{
json = try JSONSerialization.jsonObject(with: data, options: [])
}
catch
{
return
}
guard let data_list = json as? NSArray else
{
return
}
if let countries_list = json as? NSArray
{
for i in 0 ..< data_list.count
{
if let country_obj = countries_list[i] as? NSDictionary
{
if let country_name = country_obj["country"] as? String
{
if let country_code = country_obj["code"] as? String
{
TableData.append(country_name + " [" + country_code + "]")
}
}
}
}
}
DispatchQueue.main.async(execute: {self.do_table_refresh()})
}
func do_table_refresh()
{
tableview.reloadData()
}
}
you need to set tableView's dataSource and delegate to self.