I'm trying to fetch data from an API through an HTTP get method in Swift 5. It successfully loads the data on launch, but when I refresh the page it says the "index is out of range", this is because the data is no longer be fetched in my logged, hence there is nothing in the index. Is this a common issue or does this just pertain to me? I've attached my code below and have highlighted where the error shows up. Thank you!
import UIKit
import Foundation
class TheClass: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var pageView: UITableView!
struct StocksObject {
var volume: String?
}
struct Response: Codable {
let _type: String?
let readLink: String?
let totalEstimatedMatches: Int?
let value: [Value]?
}
struct QueryContext: Codable {
let originalQuery: String?
let adultIntent: Bool?
}
struct Value: Codable {
let name: String?
}
var stocks_object = [StocksObject]()
var news_object = [Value]()
override func viewDidAppear(_ animated: Bool) {
self.news_object.removeAll()
self.getNews()
}
override func viewDidLoad() {
super.viewDidLoad()
getData()
getNews()
pageView.delegate = self
pageView.dataSource = self
}
func getNews() {
let request = NSMutableURLRequest(url: NSURL(string: "https://example.com")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
guard let data = data else { return }
do {
let obj: Response = try JSONDecoder().decode(Response.self, from: data)
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
let objectReview = Value(name: obj.value?[0].name)
self.news_object.append(objectReview)
print("\(String(describing: obj.value?[0].name))")
print("\(String(describing: obj.value?[2].name))")
} catch {
print("ERROR: \(error)")
self.performSegue(withIdentifier: "InvalidSymbol", sender: nil)
}
}.resume()
}
func getData() {
let url = URL(string: "https://example2.com")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
NSURLConnection.sendAsynchronousRequest(request, queue: OperationQueue.main) {
(response, data, error) in
if let data = data {
do {
let json = try JSONDecoder().decode(Root.self,from: data)
let Volume = json.data.first?.volume
struct Root: Codable {
let data: [Datum]
}
struct Datum: Codable {
let volume: String
}
print(json)
let object = StocksObject(volume: Volume)
self.stocks_object.append(object)
DispatchQueue.main.async {
self.pageView.reloadData()
}
} catch {
print(error)
}
}
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 825
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return stocks_object.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! StockCell
let object = stocks_object[indexPath.row]
//Error is coming in here:
let objectNews = news_object[indexPath.row]
cell.volume.text = object.volume
cell.newsOne.text = objectNews.name
return cell
}
}
Inside viewDidAppear, you need to empty your "news_object" Array when page loads and then call self.getNews() inside viewDidAppear.
Inside viewDidAppear:
self.news_object.removeAll()
self.getNews()
Since you edited your question so here is the reason why you are facing this error.
For above code you need to create switch statement that check which array.count to return and do same in cellForRowAt. Because they both have different length that's the reason you are facing error "Out of Range"
I hope its clear now.
Related
I have a problem with data loading and view drawing in swift. My app is getting data from the server, but before I get data the table view is loading, that's why I need to reload it few times, this looks strange for me as a new developer, I do not think this should work that way. So, my question is how to load data from the server before the view is loaded. Because in my way it is easy to crash the app, just tapping before the data loaded. I tried DispatchQeue in different ways, but do not get how to solve this problem.
Here is the code on how I get data from the server and update my table view.
import Foundation
typealias OnApiSuccesPeople = (people) -> Void
typealias OnApiError = (String) -> Void
typealias OnApiSuccesPlanet = (planet) -> Void
struct ApiService {
static let shared = ApiService()
let URL_BASE = "https://swapi.dev/api"
let URL_PEOPLE = "/people"
let URL_PEOPLE_PAGE_NUMBERFROMCYCLE = "/?page="
let URL_PLANETS = "/planets"
let session = URLSession(configuration: .default)
func getResultsFromURL(onSuccess: #escaping OnApiSuccesPeople, onError: #escaping OnApiError, urlStringType: String) {
let url = URL(string: "\(urlStringType)")!
var request = URLRequest(url: url)
request.httpMethod = "GET" // GET, PUT, POST, DELETE for some different api
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
let task = session.dataTask(with: request) { (data, response, error) in
DispatchQueue.main.async {
if let error = error {
onError(error.localizedDescription)
return
}
guard let data = data, let response = response as? HTTPURLResponse else {
onError("Invalid data or response")
return
}
do{
if response.statusCode == 200 {
let results = try JSONDecoder().decode(people.self, from: data)
onSuccess(results)
} else {
let err = try JSONDecoder().decode(APIError.self, from: data)
print("Code is \(response.statusCode)")
onError(err.detail)
}
}
catch {
onError(error.localizedDescription)
}
}
}
task.resume()
}
import UIKit
class MainVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var charNameTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
getResults()
getPlanets()
charNameTable.delegate = self
charNameTable.dataSource = self
}
func getResults() {
ApiService.shared.getResultsFromURL(onSuccess: { (people) in
results.append(contentsOf: people.results)
results.sort {$0.name < $1.name}
self.charNameTable.reloadData()
}, onError: { (error) in
debugPrint(error)
}, urlStringType: "\(ApiService.shared.URL_BASE)\(ApiService.shared.URL_PEOPLE)")
for i in 2...9 {
ApiService.shared.getResultsFromURL(onSuccess: { (people) in
results.append(contentsOf: people.results)
results.sort {$0.name < $1.name}
self.charNameTable.reloadData()
}, onError: { (error) in
debugPrint(error)
}, urlStringType: "\(ApiService.shared.URL_BASE)\(ApiService.shared.URL_PEOPLE)\(ApiService.shared.URL_PEOPLE_PAGE_NUMBERFROMCYCLE)\(i)")
}
}
func getPlanets() {
for i in 1...60 {
ApiService.shared.getPlanetsFromURL(onSuccess: { (planet) in
resultsPlanets.append(planet)
}, onError: { (error) in
debugPrint(error)
}, urlStringType: "\(ApiService.shared.URL_BASE)\(ApiService.shared.URL_PLANETS)/\(i)")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return results.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "CharacterCell") as? CharacterCell {
cell.updatecell(resuls: results[indexPath.row])
return cell
}
return UITableViewCell()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let detail = results[indexPath.row]
performSegue(withIdentifier: "DetailedVC", sender: detail)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
initDetailedResults()
if let detailedVC = segue.destination as? DetailedVC {
detailedVC.initDetailedResultsForScreen(result: sender as! result)
}
}
}
Use Combine framework to Publish and Subscribe
import Combine
class ApiService: ObservableObject {
#Published var items = Array<people>()
// ...
items = try JSONDecoder().decode(people.self, from: data)
// ...
}
Then you can listen for items in your controller and update UITableView
import Combine
class MainVC: UIViewController {
// ....
private var cancellables = Set<AnyCancellable>()
// ...
override func viewDidLoad() {
super.viewDidLoad()
getResults()
getPlanets()
charNameTable.delegate = self
charNameTable.dataSource = self
ApiService.shared.$items
.receive(on: DispatchQueue.main)
.sink { [weak self] people in
self?.results = people
self?.charNameTable.reloadData()
}
.store(in: &cancellables)
}
}
I'm working with CocktailDB.
By creating a request I get a JSON file, parse it with Decodable protocol. From JSON I get all drinks' categories and display them as the sections of my tableview.
In each tableview section I want to display drinks from specific category (section's header). One drink per section cell from the category (drink's strDrink (name) and strDrinkThumb (image)).
I have a method that creates a request to get drinks from specific category - getDrinksFrom(category: String).
Please advice how can I call this method for specific section to get and display drinks from specific category in this section?
My code:
class ViewController: UIViewController {
var drinks = [Drink]()
var categories = [Category]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
getCategories()
getDrinksFrom(category: "Cocoa")
}
func getCategories() {
let url = URL(string: "https://www.thecocktaildb.com/api/json/v1/1/list.php?c=list")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil {
do {
self.categories = try JSONDecoder().decode(Categories.self, from: data!).drinks
DispatchQueue.main.async {
self.tableView.reloadData()
}
print(self.categories)
} catch {
print(error)
}
}
}.resume()
}
func getDrinksFrom(category: String) {
let url = URL(string: "https://www.thecocktaildb.com/api/json/v1/1/filter.php?c=\(category)")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil {
do {
self.drinks = try JSONDecoder().decode(Drinks.self, from: data!).drinks
DispatchQueue.main.async {
self.tableView.reloadData()
}
print(self.drinks)
} catch {
print(error)
}
}
}.resume()
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return categories.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return categories[section].strCategory
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "drinkCell") as! DrinkCell
cell.drinkName.text = drinks[indexPath.row].strDrink
let url = drinks[indexPath.row].strDrinkThumb
cell.drinkImage.downloaded(from: url)
return cell
}
}
// to download an image from web
extension UIImageView {
func downloaded(from url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
contentMode = mode
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else { return }
DispatchQueue.main.async() { [weak self] in
self?.image = image
}
}.resume()
}
func downloaded(from link: String, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
guard let url = URL(string: link) else { return }
downloaded(from: url, contentMode: mode)
}
}
Category Model:
struct Categories:Decodable {
var drinks: [Category]
}
struct Category:Decodable {
var strCategory: String
}
Drink Model:
struct Drinks:Decodable {
var drinks: [Drink]
}
struct Drink:Decodable {
var strDrink: String
var strDrinkThumb: String
}
What I have for know:
JSON structure:
My suggestion is to create a custom struct Category with name and drinks for the sections. It does not conform to Decodable, this is intended
struct Category {
let name : String
var drinks : [Drink]
}
and an appropriate data source array
var categories = [Category]()
then load and parse the categories with traditional JSONSerialization and populate the array by mapping the names. Further add a completion handler
func getCategories(completion: #escaping () -> Void) {
let url = URL(string: "https://www.thecocktaildb.com/api/json/v1/1/list.php?c=list")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if let error = error { print(error); return }
do {
let result = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
let categoryNames = result["drinks"] as! [[String:String]]
self.categories = categoryNames.map{ Category(name: $0["strCategory"]!, drinks:[])}
completion()
} catch {
print(error)
}
}.resume()
}
To avoid naming confusion (too many drinks) name the root struct Response
struct Response : Decodable {
let drinks: [Drink]
}
Load the data related to a category and assign the drinks array to the corresponding array in categories
func getDrinksFrom(category: String) {
let url = URL(string: "https://www.thecocktaildb.com/api/json/v1/1/filter.php?c=\(category)")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if let error = error { print(error); return }
do {
let drinks = try JSONDecoder().decode(Response.self, from: data!).drinks
guard let index = categories.firstIndex(where: {$0.name == category}) else { return }
self.categories[index].drinks = drinks
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}.resume()
}
and replace viewDidLoad with
override func viewDidLoad() {
super.viewDidLoad()
getCategories { [weak self] in
self?.getDrinksFrom(category: "Cocoa")
}
}
Finally change the table view data source methods to match the section structure
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return categories.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return categories[section].name
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categories[section].drinks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "drinkCell") as! DrinkCell
let category = categories[indexPath.section]
let drink = category.drinks[indexPath.row]
cell.drinkName.text = drink.strDrink
let url = drink.strDrinkThumb
cell.drinkImage.downloaded(from: url)
return cell
}
}
You can also put both functions together and load all drinks for all categories
func loadAllCategories() {
let url = URL(string: "https://www.thecocktaildb.com/api/json/v1/1/list.php?c=list")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if let error = error { print(error); return }
do {
let result = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
let categoryNames = (result["drinks"] as! [[String:String]]).map{$0["strCategory"]!}
let group = DispatchGroup()
for category in categoryNames {
let categoryURLString = "https://www.thecocktaildb.com/api/json/v1/1/filter.php?c=\(category)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let categoryURL = URL(string: categoryURLString)!
group.enter()
let categoryTask = URLSession.shared.dataTask(with: categoryURL) { (categoryData, _, categoryError) in
defer { group.leave() }
if let categoryError = categoryError { print(categoryError); return }
do {
let drinks = try JSONDecoder().decode(Response.self, from: categoryData!).drinks
self.categories.append(Category(name: category, drinks: drinks))
} catch {
print(error)
}
}
categoryTask.resume()
}
group.notify(queue: .main) {
self.tableView.reloadData()
}
} catch {
print(error)
}
}.resume()
}
This is just a pseudocode, which will give you an idea how you can proceed further. The code has not been tested.
Create an array of sections to be loaded.
var sections: [Sections] = []
In you tableview delegates you can create a struct for the sections that you need to load, which will help you to identify the section in cell for row index path where you can call API based on categories.
extension ViewController: UITableViewDataSource, UITableViewDelegate {
struct Sections {
static var count = 0
// In stantiate table view headers index order
enum SectionType {
case SoftDrink
case OrdinaryDrink
case MilkShake
}
var type: SectionType?
var section: Int?
var rows: Int?
}
func setUpTableView() {
// Set Up Tableview Data
if check if Drink is type of SoftDrink /*If you sections are loaded dynamic u can add condition*/ {
sections.append(Sections(type: .SoftDrink, section: Sections.count, rows: 1))
Sections.count += 1
}
Sections.count = 0
}
func numberOfSections(in _: UITableView) -> Int {
sections.count
}
func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
sections[section].rows ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var tableCell: UITableViewCell = UITableViewCell()
guard let type = sections[indexPath.section].type else {
tableCell.selectionStyle = .none
return tableCell
}
switch type {
case .SoftDrink: break
// Instantiate cell and API calls.
case .OrdinaryDrink: break
// Instantiate cell and API calls.
case .MilkShake: break
// Instantiate cell and API calls.
}
tableCell.selectionStyle = .none
return tableCell
}
}
setUpTableView() can be called in viewDidLoad Method.
I'm having troubles parsing images into my table view reusable cell. Hope anyone can help. Sorry if there is gonna be too many code, just don't want to miss something. Here is what my code look like:
import UIKit
struct OfferList: Decodable {
let data: [CarItems]
let status: String
let count: Int }
struct CarItems: Decodable {
let id: String
let image: URL
let manufacturer: String
let model: String
let priceNet: Double
let priceOld: Int
let priceGross: Double
let powerKw: String
let powerPs: String
let milage: String
let fueltype: String }
class OfferVC: UIViewController {
#IBOutlet weak var tableView: UITableView!
var viewModels = [CarItems]()
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "http://grandex.de/api/v1/de/offers"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, error) in
if let error = error { print(error); return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(OfferList.self, from: data!)
self.viewModels = result.data
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}.resume()
}
}
extension OfferVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModels.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "id") as! Cell
let vm = viewModels[indexPath.row]
cell.update(with: vm)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 130
} }
class Cell: UITableViewCell {
#IBOutlet weak var carImage: UIImageView!
#IBOutlet weak var title: UILabel!
#IBOutlet weak var fueltype: UILabel!
func update(with item: CarItems) {
title?.text = item.manufacturer
fueltype?.text = item.fueltype
carImage?.image = item.image
}
func getImage(with item: CarItems){
let session = URLSession(configuration: URLSessionConfiguration.default)
guard let url = URL(string: "\(item.image)") else { return }
var request = URLRequest(url: url)
request.httpMethod = "GET"
session.dataTask(with: request) { (data, response, error) in
if let error = error {
print("Something went wrong: \(error)")
}
if let imageData = data {
DispatchQueue.main.async {
self.carImage.image = UIImage(data: data!)
}
}
}.resume()
} }
I'm expecting it to add images to my cells, but it doesn't work.
Any help would be much appreciated! Thanks in advance!
First of all, in CarItems struct, set the type of image as String instead of URL. You get a String from JSON and not a URL type.
In update(with:) method, call getImage(with:) method, i.e.
func update(with item: CarItems) {
title?.text = item.manufacturer
fueltype?.text = item.fueltype
self.getImage(with: item) //Here..
}
Also, keep the code as short as possible. You can cut down the getImage(with:) method to:
func getImage(with item: CarItems){
guard let url = URL(string: "\(item.image)") else { return }
URLSession.shared.dataTask(with: url) {[weak self] (data, response, error) in
if let data = data {
DispatchQueue.main.async {
self?.carImage.image = UIImage(data: data)
}
}
}.resume()
}
I already new in swift 3 and objetive c, right now I am stuck into how can I pass the id of each row to another table view controller when the user tap in the row the user want to go.
Here is the json data firstFile:
[
{"id_categoria":"1","totalRows":"323","nombre_categoria":"Cirug\u00eda"},
{"id_categoria":"2","totalRows":"312","nombre_categoria":"Med Interna"},
{"id_categoria":"3","totalRows":"6","nombre_categoria":"Anatomia"},
{"id_categoria":"4","totalRows":"24","nombre_categoria":"Anestesiologia"},
...]
Here is my first table view controller:
import UIKit
class CatMedVC: UIViewController, UITableViewDataSource {
#IBAction func volver(_ sender: Any) { }
#IBOutlet weak var listaCategoria: UITableView!
var fetchedCategoria = [Categoria]()
override func viewDidLoad() {
super.viewDidLoad()
listaCategoria.dataSource = self
parseData()
}
override var prefersStatusBarHidden: Bool{
return true
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fetchedCategoria.count
}
public func tableView(_ tableView: UITableView, cellForRowAt IndexPath: IndexPath) ->
UITableViewCell {
let cell = listaCategoria.dequeueReusableCell(withIdentifier: "cell")
cell?.textLabel?.text = fetchedCategoria[IndexPath.row].esp
cell?.detailTextLabel?.text = fetchedCategoria [IndexPath.row].totalRows
return cell!
}
func parseData() {
let url = "http://www.url.com/firstFile.php" //in json format
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "GET"
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: nil, delegateQueue: OperationQueue.main)
let task = session.dataTask(with: request) { (data, response, error) in
if(error != nil) {
print("Error")
}
else {
do {
let fetchedData = try JSONSerialization.jsonObject(with:data!, options: .mutableLeaves) as! NSArray
//print(fetchedData)
for eachFetchedCategoria in fetchedData {
let eachCategoria = eachFetchedCategoria as! [String : Any]
let nombre_categoria = eachCategoria["nombre_categoria"] as! String
let totalRows = eachCategoria["totalRows"] as! String
let id_categoria = eachCategoria["id_categoria"] as! String
self.fetchedCategoria.append(Categoria(nombre_categoria: nombre_categoria, totalRows: totalRows, id_categoria: id_categoria))
}
//print(self.fetchedCategoria)
self.listaCategoria.reloadData()
}
catch {
print("Error 2")
}
}
}
task.resume()
}
}
class Categoria {
var nombre_categoria : String
var totalRows : String
var id_categoria : String
init(nombre_categoria : String, totalRows : String, id_categoria : String) {
self.nombre_categoria = nombre_categoria
self.totalRows = totalRows
self.id_categoria = id_categoria
}
}
So I need pass the id_categoria String into the another table view to show the data for the id selected previously...here I don't know how to do it...I have the json file waiting for the id selected previously..but I don't know how to catch it into the url
Here the second table view:
import UIKit
class EspMedVC: UITableViewController {
var TableData:Array< String > = Array < String >()
var EspecialidadArray = [String]()
#IBAction func volver(_ sender: Any) {
}
override func viewDidLoad() {
super.viewDidLoad()
get_data_from_url("http://www.url.com/secondFile.php?id=") // Here I need to put the id_categoria String in json format
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return TableData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell2", 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 nombre_especialidad = json as? NSArray
{
for i in 0 ..< data_list.count
{
if let nombre_esp_obj = nombre_especialidad[i] as? NSDictionary
{
if let nombre_especialidad = nombre_esp_obj["subesp"] as? String
{
if let totalRows = nombre_esp_obj["totalRows"] as? String
{
TableData.append(nombre_especialidad + " [" + totalRows + "]")
}
}
}
}
}
DispatchQueue.main.async(execute: {self.do_table_refresh()})
}
func do_table_refresh()
{
self.tableView.reloadData()
}
}
This is a rough guide, please search for the methods in the documentation or here at other questions inside stackoverflow.
1) Add a variable inside your EspMedVC that will hold the "id_categoria String" that should be displayed.
2) Add a variable inside your CatMedVC that will hold the "id_categoria String" that the user selected.
3) Implement the "didSelectRow" delegate method from your tableview inside the "CatMedVC", inside this method you should set the variable set on step 2.
4) Implement the "prepareForSegue" method inside your CatMedVC, inside the the implementation you should retrieve the destination VC, cast it to "EspMedVC" and set the variable from step 1.
5) On the "viewDidLoad" from EspMedVC you can now use the variable set on step 2 to query your JSON and update the table view accordingly.
I am new to Swift, and am trying to create a table that reads JSON data from my website. I followed some tutorials, and was able to get my code to work with a table controller. Now I'm trying to use a view controller with a table view inside, so I can have more customization. My problem is, I can't get the data to actually show up when I try to use my new code.
This is what I have in my viewController.swift:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var TableData:Array< String > = Array < String >()
override func viewDidLoad() {
super.viewDidLoad()
get_data_from_url("http://www.stevenbunting.org/Alliris/service.php")
}
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: "cell", 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["user"] as? String
{
if let country_code = country_obj["friendlist"] as? String
{
TableData.append(country_name + " [" + country_code + "]")
}
}
}
}
}
DispatchQueue.main.async(execute: {self.do_table_refresh()
})
}
func do_table_refresh()
{
self.tableView.reloadData()
}
}
Probably you didn't set the tableView's dataSource. To do this, implement the UITableViewDataSource-protocol in the ViewController-class and set the tableView's dataSource-property to self in the viewDidLoad(), for example:
class ViewController: UIViewController, UITableViewDataSource {
// ...
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
// ...
}
//...
}
Oh, and don't forget about the Apple Transport Security-settings, otherwise you won't see anything as iOS doesn't allow HTTP anymore, you have use HTTPS. The right way to handle this is to get an SSL-Certificate for your domain.
The quick'n'dirty and absolutely not recommended way is to disable ATS or to set an exception for certain, trustworthy domains.