Please advice how can I paginate my tableview. I get data from the CocktailDB API and parse JSON with Decodable. There are categories and drinks from these categories.
I want to firstly load 10 drinks and then load more when I scroll to the bottom of the page.
I considered using willDisplay method by tableview.
Thank you in advance!
My code:
class ViewController: UIViewController {
struct OneCategory {
let name : String
var drinks : [Drink]
}
var drinks = [Drink]()
var categories = [OneCategory]()
var selectedCategoriesArr: [String] = []
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
loadAllCategories()
}
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(OneCategory(name: category, drinks: drinks))
} catch {
print(error)
}
}
categoryTask.resume()
}
group.notify(queue: .main) {
self.tableView.reloadData()
}
} 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].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
}
}
Models:
struct Response:Decodable {
var drinks: [Drink]
}
struct Drink:Decodable {
var strDrink: String
var strDrinkThumb: String
}
struct Categories:Decodable {
var drinks: [Category]
}
struct Category:Decodable {
var strCategory: String
}
Related
I've been researching and wrecking my brain attempting to get my JSON data to load into my tableview. I've tried placing the data in a Variable & I'm able to see the data in the console when I print it, however unable to push it to my table view.
Am I doing something wrong on the data page or am I not properly accessing the data within the loop?
I've tried putting the loop in the viewdidload but haven't been successful either.
// ViewController
import Foundation
import UIKit
import SDWebImage
class EntertainmentViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var A = EntertainmentApi()
var data = [EntertainmentPageData]()
var AA = EntertainmentApi().userFeedPosts
#IBOutlet weak var entPostTableView: UITableView!
override func viewDidLoad() {
func showTable() {
}
entPostTableView.register(EntertainmentViewrTableViewCell.nib(), forCellReuseIdentifier: EntertainmentViewrTableViewCell.identifier)
entPostTableView.delegate = self
entPostTableView.dataSource = self
super.viewDidLoad()
DispatchQueue.main.async {
self.entPostTableView.reloadData()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let customCell1 = tableView.dequeueReusableCell(withIdentifier: EntertainmentViewrTableViewCell.identifier, for: indexPath) as! EntertainmentViewrTableViewCell
customCell1.profileDisplayName.text = AA[indexPath.row].postDisplayName
self.AA.forEach({ (EntertainmentPageData) in
customCell1.configue(with: EntertainmentPageData.postDisplayName, PostImage: EntertainmentPageData.imageURLString, PostDescription: EntertainmentPageData.postDescription)
})
return customCell1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func item(for index: Int) -> EntertainmentPageData {
return data[index]
}
func numberOfItems() -> Int {
return data.count
}
}
//Data
import SwiftUI
import SDWebImage
public protocol EntertainmentPagePostItem {
/// The image for the card.
var imageURLString: String { get }
/// Rating from 0 to 5. If set to nil, rating view will not be displayed for the card.
var postDescription: String? { get }
/// Will be displayed in the title view below the card.
var postDisplayName: String { get }
}
public protocol EntertainmentPagePostDataSource: class {
/// CardSliderItem for the card at given index, counting from the top.
func item(for index: Int) -> EntertainmentPagePostItem
/// Total number of cards.
func numberOfItems() -> Int
}
struct HomePagePost: Codable {
var displayName: String
var cityStatus: String
var displayDescription: String
var displayImageURL: String
var lookingFor: String
var profileImager1: String?
var profileImager2: String?
var profileImager3: String?
var profileImager4: String?
}
struct EntertainmentPageData: Codable {
let postDisplayName: String
let imageURLString: String
let postDescription: String?
}
public class entPostFly: Codable {
let postDisplayName, imageURLString, postDescription: String
}
struct eItem: EntertainmentPagePostItem {
var postDisplayName: String
var imageURLString: String
var postDescription: String?
}
public class EntertainmentApi {
var userFeedPosts = [EntertainmentPageData]()
init() {
load()
}
func load() {
guard let apiURL = URL(string: "https://api.quickques.com/....") else {
return
}
let task: () = URLSession.shared.dataTask(with: apiURL) { Data, apiResponse, error in
guard let Data = Data else { return }
do {
let entPostData = try JSONDecoder().decode([EntertainmentPageData].self, from: Data)
self.userFeedPosts = entPostData
}
catch {
let error = error
print(error.localizedDescription)
}
}.resume()
}
func getFeedPosts(completion: #escaping ([EntertainmentPageData]) -> () ) {
guard let apiURL = URL(string: "https://api.quickques.com/....") else {
return
}
let task: () = URLSession.shared.dataTask(with: apiURL) { Data, apiResponse, error in
guard let Data = Data else { return }
do {
let entPostData = try JSONDecoder().decode([EntertainmentPageData].self, from: Data)
completion(entPostData)
}
catch {
let error = error
print(error.localizedDescription)
}
}.resume()
}
}
class Api {
func getHomePagePosts(completion: #escaping ([HomePagePost]) -> Void ) {
guard let apiURL = URL(string: "https://api.quickques.com/.....") else {
return
}
let task: () = URLSession.shared.dataTask(with: apiURL) { Data, apiResponse, error in
guard let Data = Data else { return }
do {
let homePostData = try JSONDecoder().decode([HomePagePost].self, from: Data)
completion(homePostData)
}
catch {
let error = error
print(error.localizedDescription)
}
}.resume()
}
func getImageData(from url: URL, completion: #escaping (Data?, URLResponse?, Error?) -> ()) {
URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
}
}
func getTopMostViewController() -> UIViewController? {
var topMostViewController = UIApplication.shared.keyWindow?.rootViewController
while let presentedViewController = topMostViewController?.presentedViewController {
topMostViewController = presentedViewController
}
return topMostViewController
}
First you have an empty function showTable inside your viewDidLoad - This does nothing. Presumably it is something hanging around from your various attempts. Delete that.
As you have probably worked out, your network fetch operation is going to occur asynchronously and you need to reload the table view once the data has been fetched.
You have some code in viewDidLoad that kind of tries to do this, but it isn't related to the fetch operation. It is just dispatched asynchronously on the next run loop cycle; This is probably still before the data has been fetched.
However, even if the data has been fetched, it won't show up because you are assigning userFeedPosts from a second instance of your API object to AA at initialisation time. This array is empty and will remain empty since Swift arrays are value types, not reference types. When userFeedPosts is updated, AA will hold the original empty array.
To load the data you need to
Start a load operation when the view loads
Pass a completion handler to that load operation to be invoked when the load is complete
Reload your table view with the new data
class EntertainmentViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var data = [EntertainmentPageData]()
#IBOutlet weak var entPostTableView: UITableView!
override func viewDidLoad() {
entPostTableView.register(EntertainmentViewrTableViewCell.nib(), forCellReuseIdentifier: EntertainmentViewrTableViewCell.identifier)
entPostTableView.delegate = self
entPostTableView.dataSource = self
super.viewDidLoad()
EntertainmentAPI.getFeedPosts { result in
DispatchQueue.main.async { // Ensure UI updates on main queue
switch result {
case .error(let error):
print("There was an error: \(error)")
case .success(let data):
self.data = data
self.entPostTableView.reloadData
}
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let customCell1 = tableView.dequeueReusableCell(withIdentifier: EntertainmentViewrTableViewCell.identifier, for: indexPath) as! EntertainmentViewrTableViewCell
let post = data[indexPath.row)
customCell1.profileDisplayName.text = data[indexPath.row].postDisplayName
customCell1.configure(with: post.postDisplayName, PostImage: post.imageURLString, PostDescription: post.postDescription)
return customCell1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
}
public class EntertainmentAPI {
static func getFeedPosts(completion: #escaping ((Result<[EntertainmentPageData],Error>) -> Void) ) {
guard let apiURL = URL(string: "https://api.quickques.com/....") else {
return
}
let task = URLSession.shared.dataTask(with: apiURL) { data, apiResponse, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
/// TODO - Invoke the completion handler with a .failure case
return
}
do {
let entPostData = try JSONDecoder().decode([EntertainmentPageData].self, from: Data)
completion(.success(entPostData))
}
catch {
completion(.failure(error))
}
}.resume()
}
}
I'm trying to save data to the core data and then display it on another view controller. I have a table view with custom cell, which have a button. I've created a selector, so when we tap on the button in each of the cell, it should save all the data from the cell. Here is my parent view controller:
import UIKit
import SafariServices
import CoreData
class ViewController: UIViewController, UISearchBarDelegate {
#IBOutlet weak var pecodeTableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var savedNews = [SavedNews]()
var newsTitle: String?
var newsAuthor: String?
var urlString: String?
var newsDate: String?
var isSaved: Bool = false
private var articles = [Article]()
private var viewModels = [NewsTableViewCellViewModel]()
private let searchVC = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
pecodeTableView.delegate = self
pecodeTableView.dataSource = self
pecodeTableView.register(UINib(nibName: S.CustomCell.customNewsCell, bundle: nil), forCellReuseIdentifier: S.CustomCell.customCellIdentifier)
fetchAllNews()
createSearchBar()
loadNews()
saveNews()
countNewsToCategory()
}
#IBAction func goToFavouritesNews(_ sender: UIButton) {
performSegue(withIdentifier: S.Segues.goToFav, sender: self)
}
private func fetchAllNews() {
APICaller.shared.getAllStories { [weak self] result in
switch result {
case .success(let articles):
self?.articles = articles
self?.viewModels = articles.compactMap({
NewsTableViewCellViewModel(author: $0.author ?? "Unknown", title: $0.title, subtitle: $0.description ?? "No description", imageURL: URL(string: $0.urlToImage ?? "")
)
})
DispatchQueue.main.async {
self?.pecodeTableView.reloadData()
}
case .failure(let error):
print(error)
}
}
}
private func createSearchBar() {
navigationItem.searchController = searchVC
searchVC.searchBar.delegate = self
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModels.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: S.CustomCell.customCellIdentifier, for: indexPath) as! CustomNewsCell
cell.configure(with: viewModels[indexPath.row])
cell.saveNewsBtn.tag = indexPath.row
cell.saveNewsBtn.addTarget(self, action: #selector(didTapCellButton(sender:)), for: .touchUpInside)
return cell
}
#objc func didTapCellButton(sender: UIButton) {
guard viewModels.indices.contains(sender.tag) else { return }
print("Done")// check element exist in tableview datasource
if !isSaved {
saveNews()
print("success")
}
//Configure selected button or update model
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let article = articles[indexPath.row]
guard let url = URL(string: article.url ?? "") else {
return
}
let vc = SFSafariViewController(url: url)
present(vc, animated: true)
}
//Search
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let text = searchBar.text, !text.isEmpty else {
return
}
APICaller.shared.Search(with: text) { [weak self] result in
switch result {
case .success(let articles):
self?.articles = articles
self?.viewModels = articles.compactMap({
NewsTableViewCellViewModel(author: $0.author ?? "Unknown", title: $0.title, subtitle: $0.description ?? "No description", imageURL: URL(string: $0.urlToImage ?? "")
)
})
DispatchQueue.main.async {
self?.pecodeTableView.reloadData()
self?.searchVC.dismiss(animated: true, completion: nil)
}
case .failure(let error):
print(error)
}
}
}
}
extension ViewController {
func loadNews() {
let request: NSFetchRequest<SavedNews> = SavedNews.fetchRequest()
do {
let savedNews = try context.fetch(request)
//Handle saved news
if savedNews.count > 0 {
isSaved = true
}
} catch {
print("Error fetching data from context \(error)")
}
}
func saveNews() {
//Initialize the context
let news = SavedNews(context: self.context)
//Putting data
news.title = newsTitle
news.author = newsAuthor
news.publishedAt = newsDate
news.url = urlString
do {
try context.save()
} catch {
print("Error when saving data \(error)")
}
}
func countNewsToCategory() {
//Initialize the context
let request: NSFetchRequest<SavedNews> = SavedNews.fetchRequest()
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
])
request.predicate = predicate
do {
savedNews = try context.fetch(request)
} catch {
print("Error fetching data from category \(error)")
}
}
}
I don't know where is the problem, I've created a correct data model, but data could not be saved. Here is my model:
import Foundation
struct APIResponse: Codable {
let articles: [Article]
}
struct Article: Codable {
let author: String?
let source: Source
let title: String
let description: String?
let url: String?
let urlToImage: String?
let publishedAt: String
}
struct Source: Codable {
let name: String
}
And also my model in Core Data:
My second view controller, to which I want display the data:
import UIKit
import CoreData
class FavouriteNewsViewController: UIViewController {
#IBOutlet weak var favTableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var savedNews = [SavedNews]()
override func viewDidLoad() {
super.viewDidLoad()
favTableView.delegate = self
favTableView.delegate = self
loadSavedNews()
favTableView.register(UINib(nibName: S.FavouriteCell.favouriteCell, bundle: nil), forCellReuseIdentifier: S.FavouriteCell.favouriteCellIdentifier)
// Do any additional setup after loading the view.
}
}
extension FavouriteNewsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return savedNews.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = favTableView.dequeueReusableCell(withIdentifier: S.FavouriteCell.favouriteCellIdentifier, for: indexPath) as! FavouritesCell
print(savedNews)
let article = savedNews[indexPath.row]
if let articleTitle = article.title {
cell.favTitle.text = articleTitle
}
if let articleAuthor = article.author {
cell.favAuthor.text = articleAuthor
}
if let articleDesc = article.desc {
cell.favDesc.text = article.desc
}
return cell
}
}
extension FavouriteNewsViewController {
func loadSavedNews() {
let request: NSFetchRequest<SavedNews> = SavedNews.fetchRequest()
do {
savedNews = try context.fetch(request)
} catch {
print("Error fetching data from context \(error)")
}
}
func deleteNews(at indexPath: IndexPath) {
// Delete From NSObject
context.delete(savedNews[indexPath.row])
// Delete From current News list
savedNews.remove(at: indexPath.row)
// Save deletion
do {
try context.save()
} catch {
print("Error when saving data \(error)")
}
}
}
you did not assign your properties that you are trying to save
newsTitle,newsAuthor,newsDate,urlString
seems these properties have nil value . make sure these properties have valid value before save .
Hello good afternoon to everyone.
I hope you are doing very well despite the situation we are going through.
I tell you my problem.
I am trying to save in an array, a series of data that is selected from a tableView through a JSON.
That is, the tableView shows some data available to select, the ones that are selected I want to save them in an array but when I select a data in the tableView my app crashes and shows me a breakpoint "Thread 1: signal SIGABRT"
And in the console I get this:
Could not cast value of type 'MallConcierge.DetallesIntereses' (0x10ca9be10) to 'NSString' (0x7fff86d8bbb0).
I hope you can help me, I attach the classes from where I download the data, the details and the class where I connect the tableView.
InteresesModelo.swift (in this class is where I download the data in JSON)
import UIKit
protocol InteresesModeloProtocol: class{
func interesesDownload (interest: NSArray)
}
class InteresesModelo: NSObject {
weak var delegate: InteresesModeloProtocol!
let urlPath = "http://localhost:8888/mallconcierge/API-movil/interests.php"
func interestDownload(){
let url: URL = URL(string: urlPath)!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.ephemeral)
URLCache.shared.removeAllCachedResponses()
let task = defaultSession.dataTask(with: url){
(data, response, error) in
if error != nil{
print("Error al descargar datos")
}else{
print("Datos descargados")
self.parseJSON(data!)
}
}
task.resume()
}
func parseJSON(_ data:Data){
var jsonResult = NSArray()
do{
jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as! NSArray
}catch let error as NSError{
print(error)
}
var jsonElement = NSDictionary()
let detalles = NSMutableArray()
for i in 0 ..< jsonResult.count{
jsonElement = jsonResult[i] as! NSDictionary
let detalle = DetallesIntereses()
let idInteres = jsonElement["idInteres"]
let nombreInteres = jsonElement["interesNombre"]
detalle.idInteres = idInteres as? String
detalle.nombreInteres = nombreInteres as? String
detalles.add(detalle)
}
DispatchQueue.main.async(execute: { ()-> Void in
self.delegate.interesesDownload(interest: detalles)
})
}
}
DetallesIntereses.swift
import UIKit
class DetallesIntereses: NSObject {
var idInteres: String?
var nombreInteres: String?
override init() {
}
init(idInteres: String, nombreInteres:String) {
self.idInteres = idInteres
self.nombreInteres = nombreInteres
}
override var description: String{
return "idInteres: \(idInteres), nombreInteres: \(nombreInteres)"
}
}
InteresesViewController.swift
import UIKit
class InteresesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, InteresesModeloProtocol {
var selectIntereses = [String]()
var feedInterests: NSArray = NSArray()
// var selectInterests: DetallesIntereses = DetallesIntereses()
var items=[String]()
#IBOutlet var listaInteresesTableView: UITableView!
func interesesDownload(interest: NSArray) {
feedInterests = interest
self.listaInteresesTableView.reloadData()
}
override func viewDidLoad() {
self.listaInteresesTableView.isEditing = true
self.listaInteresesTableView.allowsMultipleSelectionDuringEditing = true
self.listaInteresesTableView.delegate = self
self.listaInteresesTableView.dataSource = self
let interesesModelo = InteresesModelo()
interesesModelo.delegate = self
interesesModelo.interestDownload()
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return feedInterests.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "celInterests", for: indexPath) as! InteresesTableViewCell
let interest: DetallesIntereses = feedInterests[indexPath.row] as! DetallesIntereses
cell.lblNombreIntereses!.text = interest.nombreInteres
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectDeselectCell(tableView: listaInteresesTableView, indexPath: indexPath)
print("Seleccionado")
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
self.selectDeselectCell(tableView: listaInteresesTableView, indexPath: indexPath)
print("Deseleccionado")
}
func selectDeselectCell(tableView: UITableView, indexPath: IndexPath){
self.selectIntereses.removeAll()
if let arr = listaInteresesTableView.indexPathsForSelectedRows{
for index in arr{
selectIntereses.append(feedInterests[indexPath.row] as! String)
}
}
print(selectIntereses)
}
#IBAction func seleccionarIntereses(_ sender: Any){
print(selectIntereses)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
The JSON
[
{
"idInteres": "1",
"interesNombre": "Moda Mujer"
},
{
"idInteres": "3",
"interesNombre": "Moda Hombre"
},
{
"idInteres": "4",
"interesNombre": "Belleza"
},
{
"idInteres": "5",
"interesNombre": "Relojes y JoyerÃa"
},
{
"idInteres": "6",
"interesNombre": "Hogar/Interiorismo"
},
{
"idInteres": "7",
"interesNombre": "GastronomÃa"
},
{
"idInteres": "8",
"interesNombre": "Entretenimiento"
},
{
"idInteres": "9",
"interesNombre": "Wellness"
}
]
I hope you can help me, please.
I thank you all
There's lots of little stuff going on here. I've tried to highlight as I make edits with //<-- Here.
By far the biggest issue is using the untyped Objective-C NSArray instead of using a typed Swift array. By switching to that, you can get useful type errors.
I had to make a couple of assumptions about what you wanted to do, but this should get you started.
I've left the original JSON decoding for the most part, but I'd strongly suggest you look into using Codable instead of JSONSerialization unless you have a compelling reason not to.
protocol InteresesModeloProtocol : AnyObject {
func interesesDownload (interest: [DetallesIntereses]) //<-- Here
}
class InteresesModelo: NSObject {
weak var delegate: InteresesModeloProtocol!
let urlPath = "http://localhost:8888/mallconcierge/API-movil/interests.php"
func interestDownload(){
let url: URL = URL(string: urlPath)!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.ephemeral)
URLCache.shared.removeAllCachedResponses()
let task = defaultSession.dataTask(with: url){
(data, response, error) in
if error != nil{
print("Error al descargar datos")
}else{
print("Datos descargados")
self.parseJSON(data!)
}
}
task.resume()
}
func parseJSON(_ data:Data){
var jsonResult = [[String:String]]()
do{
jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as! [[String : String]]
}catch let error as NSError{
print(error)
}
let detalles = jsonResult.map { jsonElement -> DetallesIntereses in
let detalle = DetallesIntereses(idInteres: jsonElement["idInteres"], nombreInteres: jsonElement["interesNombre"])
return detalle
}
DispatchQueue.main.async {
self.delegate.interesesDownload(interest: detalles)
}
}
}
struct DetallesIntereses { //<-- Here -- no reason for this to be an `NSObject`
var idInteres: String?
var nombreInteres: String?
}
class InteresesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, InteresesModeloProtocol {
var selectIntereses = [DetallesIntereses]() //<-- Here
var feedInterests: [DetallesIntereses] = [] //<-- Here
var items = [String]()
#IBOutlet var listaInteresesTableView: UITableView!
func interesesDownload(interest: [DetallesIntereses]) { //<-- Here
feedInterests = interest //<-- Here
self.listaInteresesTableView.reloadData()
}
override func viewDidLoad() {
self.listaInteresesTableView.isEditing = true
self.listaInteresesTableView.allowsMultipleSelectionDuringEditing = true
self.listaInteresesTableView.delegate = self
self.listaInteresesTableView.dataSource = self
let interesesModelo = InteresesModelo()
interesesModelo.delegate = self
interesesModelo.interestDownload()
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return feedInterests.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "celInterests", for: indexPath) as! InteresesTableViewCell
let interest: DetallesIntereses = feedInterests[indexPath.row] //<-- Here
cell.lblNombreIntereses!.text = interest.nombreInteres
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectDeselectCell(tableView: listaInteresesTableView, indexPath: indexPath)
print("Seleccionado")
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
self.selectDeselectCell(tableView: listaInteresesTableView, indexPath: indexPath)
print("Deseleccionado")
}
func selectDeselectCell(tableView: UITableView, indexPath: IndexPath){
self.selectIntereses.removeAll()
selectIntereses.append(feedInterests[indexPath.row]) //<-- Here
print(selectIntereses)
}
#IBAction func seleccionarIntereses(_ sender: Any){
print(selectIntereses)
}
}
My entire table view is being written programmatically and the data is coming from JSON. I am trying to group the cells by the customer the code seems to be correct but no sections are showing up at all.
Here is the code:
Portfolios.swift
import UIKit
struct Portfolios: Codable {
let customer, serial, rma, model: String
let manufacturer: String
}
PortfolioController.swift
import UIKit
class PortfolioController: UITableViewController {
var portfolios = [Portfolios]()
var portfoliosDic = [String:[Portfolios]]()
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.prefersLargeTitles = true
view.backgroundColor = UIColor.blue
navigationItem.title = "Customer"
fetchJSON()
}
func fetchJSON(){
let urlString = "https://www.example.com/example/example.php"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, error) in
DispatchQueue.main.async {
if let error = error {
print("Failed to fetch data from url", error)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let res = try JSONDecoder().decode([Portfolios].self, from: data)
self.portfoliosDic = Dictionary(grouping: res, by: { $0.customer})
DispatchQueue.main.async {
self.tableView.reloadData()
}
self.portfolios = try decoder.decode([Portfolios].self, from: data)
self.tableView.reloadData()
} catch let jsonError {
print("Failed to decode json", jsonError)
}
}
}.resume()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return portfoliosDic.keys.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let keys = Array(portfoliosDic.keys)
let item = portfoliosDic[keys[section]]!
return item.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cellId")
let keys = Array(portfoliosDic.keys)
let arr = portfoliosDic[keys[indexPath.section]]!
let customer = arr[indexPath.row]
let titleStr = [customer.serial, customer.manufacturer, customer.model].compactMap { $0 }.joined(separator: " - ")
//cell.textLabel?.text = titleStr
print(titleStr)
// Get references to labels of cell
cell.textLabel!.text = customer.serial
return cell
}
}
UPDATE:
Because it is a UIViewController Xcode told me to remove the override func
and I added #IBOutlet weak var tableView: UITableView!
(The end results is an empty table for some reason)
Using a UITableViewController instead:
import UIKit
class CustomerViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var sections = [Section]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellId")
navigationController?.navigationBar.prefersLargeTitles = true
fetchJSON()
}
func fetchJSON(){
let urlString = "https://www.example.com/example/example.php"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, error) in
DispatchQueue.main.async {
if let error = error {
print("Failed to fetch data from url", error)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let res = try decoder.decode([Portfolios].self, from: data)
let grouped = Dictionary(grouping: res, by: { $0.customer })
let keys = grouped.keys.sorted()
self.sections = keys.map({Section(name: $0, items: grouped[$0]!)})
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("Failed to decode json", error)
}
}
}.resume()
}
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let section = sections[section]
return section.items.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section].name
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath)
let section = sections[indexPath.section]
let item = section.items[indexPath.row]
let titleStr = "\(item.serial) - \(item.manufacturer) - \(item.model)"
cell.textLabel!.text = titleStr
return cell
}
}
First of all why do you decode the JSON twice?
No sections are displayed because the method titleForHeaderInSection is not implemented.
The code is not reliable anyway because the order of the sections is not guaranteed. I recommend to create another struct for the sections.
struct Section {
let name : String
let items : [Portfolios]
}
struct Portfolios: Decodable {
let customer, serial, rma, model: String
let manufacturer: String
}
Delete portfolios and portfoliosDic and declare the data source array
var sections = [Section]()
Group the JSON, sort the keys and map the dictionaries to Section instances
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let res = try decoder.decode([Portfolios].self, from: data)
let grouped = Dictionary(grouping: res, by: { $0.customer })
let keys = grouped.keys.sorted()
self.sections = keys.map({Section(name: $0, items: grouped[$0]!)})
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("Failed to decode json", error)
}
The table view datasource and delegate methods are
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let section = sections[section]
return section.items.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section].name
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath)
let section = sections[indexPath.section]
let item = section.items[indexPath.row]
let titleStr = "\(item.serial) - \(item.manufacturer) - \(item.model)"
cell.textLabel!.text = titleStr
return cell
}
Note:
Always dequeue cells in cellForRowAt
I'm trying to display JSON value using Struct into table view in Swift 3.0 .
I managed to display JSON value using struct into UIPickerView and it works fine. But somehow when I use the same way in table view it does not work.
Can someone help me to update my code below in order to make it work in UITableView ? Appreciate if someone can help. Thanks.
list.php output
[
{"id":"101", "name":"name1"},
{"id":"102", "name":"name2"}
]
ViewController.swift
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
#IBOutlet var tableview: UITableView!
#IBOutlet var deptLbl: UILabel!
#IBOutlet var selectBtn: UIButton!
#IBOutlet var displayIDLbl: UILabel!
#IBOutlet var displayNameLbl: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
deptLbl.text = "ITD"
self.tableview.isHidden = true
getList()
}
var persons = [Person]()
struct Person {
var id: String
var name: String
init?(dict: [String:Any]) {
guard let id = dict["id"] as? String, let name = dict["name"] as? String else {
return nil
}
self.id = id
self.name = name
}
}
func getList() {
var request = URLRequest(url: URL(string: "http://localhost/list.php")!)
request.httpMethod = "POST"
let postString = "dept=\(deptLbl.text!)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil else {
return
}
if let array = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [[String:Any]] {
self.persons = array.flatMap(Person.init)
DispatchQueue.main.async {
self.tableview.reloadData()
}
}
}
task.resume()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self .persons.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "listcell", for: indexPath)
let data = persons[indexPath.row]
cell.textLabel?.text = data.id
cell.textLabel?.text = data.name
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.displayIDLbl.text = self.persons[indexPath.row].id
self.displayNameLbl.text = self.persons[indexPath.row].name
self.tableview.isHidden = true
}
#IBAction func pressedBtn(_ sender: Any) {
self.tableview.isHidden = !self.tableview.isHidden
}
}