How to Fetch JSON to Swift to tableView as Sections and Rows? - ios

I wanna ask how to implement the files as sections depends on userId then show all again in the tableview
I'm started build simple project i fetched json file as decoder and show all in table view
func fetchUsers(using url: String){
let url = URL(string: url)!
let _ = URLSession.shared.dataTask(with: url){ (data,response,error)
in
guard let data = data else {return}
do{
let objects = try JSONDecoder().decode([User].self, from: data) // decode * ( Codable )
self.users = objects
} catch{
print("error loading data cause: \(error)")
}
}.resume()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "users",for: indexPath) as? customeCellTableViewCell{
let indexRow = users[indexPath.row]
cell.dataModel(forModel: indexRow)
return cell
}
return UITableViewCell()
}
private func numberOfUsers(in users: [User]) -> Int {
return 1
}
func numberOfSections(in tableView: UITableView) -> Int {
return numberOfUsers(in: self.users)
}

Like #vadian mentions tuples should be avoided for this so here is an improved solution.
Instead of a tuple we can use a struct to hold the grouped data
struct UsersByID {
let id: Int
var users : [User]
}
then change the load function to
func load(withUsers users: [User]) {
let dict = Dictionary(grouping: users) { return $0.userID }
usersByID = dict.map { (key, values) in
return UsersByID(id: key, users: values)
}.sorted(by: { $0.id < $1.id })
}
The rest of the code is the same but replace key with id and value with users
Old solution
First create a dictionary to hold your sections (keys) and rows (values) as a property in the view controller
var usersByID = [(key: Int, value: [User])]()
then fill that dictionary using grouping:by: using the array from json
func load(withUsers users: [User]) {
usersByID = Dictionary(grouping: users, by: { user in
user.userID }).sorted(by: { $0.0 < $1.0})
}
then the table view functions use this dictionary
override func numberOfSections(in tableView: UITableView) -> Int {
return usersByID.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return usersByID[section].value.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return String(usersByID[section].key)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "LabelCell", for: indexPath)
let user = usersByID[indexPath.section].value[indexPath.row]
cell.textLabel?.text = user.title
//...
return cell
}

func numberOfSections(in collectionView: UICollectionView) -> Int {
return datesArr.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if (contractsDict.keys.contains(datesArr[section])) {
return contractsDict[datesArr[section]]!.count
}
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ContractsCollectionViewCell
DispatchQueue.main.async {
if (self.contractsDict.keys.contains(self.datesArr[indexPath.section])) {
for _ in 0..<(self.contractsDict[self.datesArr[indexPath.section]]!.count) {
cell.contract = self.contractsDict[self.datesArr[indexPath.section]]![indexPath.row]
cell.delegate = self
}
}
}
return cell
}
}
}
ContractServices.shared.fetchAllContracts(completion: { (contracts, err) in
DispatchQueue.main.async {
if (err != nil) {
print(err!, "1323")
return
}
for contract in (contracts?.data)! {
self.allContractsArr.append(contract)
if let callDate = contract.revdat {
let formatterGet = DateFormatter()
formatterGet.dateFormat = "yyyy-MM-dd HH:mm:ss"
let newFormat = DateFormatter()
newFormat.dateFormat = "dd MMM yyyy"
if let date = formatterGet.date(from: callDate) {
self.datesArr.append(newFormat.string(from: date))
}
}
}
for i in 0..<self.allContractsArr.count {
if let callDate = self.allContractsArr[i].revdat {
let formatterGet = DateFormatter()
formatterGet.dateFormat = "yyyy-MM-dd HH:mm:ss"
let newFormat = DateFormatter()
newFormat.dateFormat = "dd MMM yyyy"
if let date = formatterGet.date(from: callDate) {
self.allContractsArr[i].revdat = newFormat.string(from: date)
}
}
}
self.allContractsArr = self.allContractsArr.sorted(by: { ($0.revdat)! > ($1.revdat)! })
self.contractsDict = Dictionary(grouping: self.allContractsArr, by: { ($0.revdat)! })
let newFormat = DateFormatter()
newFormat.dateFormat = "dd MMM yyyy"
self.datesArr = Array(Set(self.datesArr))
self.datesArr = self.datesArr.sorted(by: { newFormat.date(from: $0)! > newFormat.date(from: $1)! })
self.contractListCV.reloadData()
DispatchQueue.main.async {
self.activityBackView.isHidden = true
self.activity.isHidden = true
self.activity.stopAnimating()
self.design()
}
}
})
This is how I'm doing.
Getting the data in array format, converting them into Dictionary by grouping(by:) method, and then operating everything related to the collectionview upon those dictionary data.

Related

Represent result of parsing JSON in tableView Swift

I start learning swift with Paul Hudson's "100 Days of Swift" and I need your advices.
I'm trying making app with country's info (capital, language, currencies etc.) and stuck with trying represent result of my JSON parsing in tableView.
This is my struct for parsing country's info from https://restcountries.com/v3.1/all
struct Country: Codable {
let name: Name
let cca2: String
let capital: [String]?
let population: Int
let currencies: [String: Currency]?
}
struct Name: Codable {
let common: String
let official: String
}
struct Currency: Codable {
let name: String?
let symbol: String?
}
I have problems with currencies. I don't understand how represent them properly in tableView. This is code of my ViewController:
import UIKit
class ViewController: UITableViewController {
var countries = [Country] ()
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://restcountries.com/v3.1/all"
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url) {
parse(json: data)
return
}
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
countries.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Country", for: indexPath)
let country = countries[indexPath.row]
cell.textLabel?.text = country.name.common
cell.imageView?.image = UIImage(named: country.cca2.lowercased())
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController {
vc.country = countries[indexPath.row]
present(vc, animated: true)
}
}
func parse(json: Data) {
let decoder = JSONDecoder()
do {
let jsonCountries = try decoder.decode([Country].self, from: json)
countries = jsonCountries
}
catch let error {
print(error)
}
}
}
And this is code of my DetailViewController:
import UIKit
class DetailViewController: UITableViewController {
var country: Country!
let flag = "Flag"
let general = "General"
let currency = "Currency"
var currencyName = ""
var currencySymbol = ""
lazy var sectionTitles = [flag, general, currency]
lazy var currencies = country.currencies?.values
override func viewDidLoad() {
super.viewDidLoad()
title = country.name.common
getCurrencyName()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return sectionTitles.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionTitles[section]
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch sectionTitles[section] {
case flag:
return 1
case general:
return 4
case currency:
// How make to return proper number's of rows??
return 2
default:
return 0
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch sectionTitles[indexPath.section] {
case flag:
let cell = tableView.dequeueReusableCell(withIdentifier: "Flag", for: indexPath)
if let cell = cell as? FlagCell {
cell.flagImageView.image = UIImage(named: country.cca2.lowercased())
}
return cell
case general:
let cell = tableView.dequeueReusableCell(withIdentifier: "Text", for: indexPath)
cell.textLabel?.numberOfLines = 0
switch indexPath.row {
case 0:
cell.textLabel?.text = "Common country name: \(country.name.common)"
case 1:
cell.textLabel?.text = "Official country name: \(country.name.official)"
case 2:
cell.textLabel?.text = "Capital: \(country.capital?[0] ?? "Unknown")"
case 3:
cell.textLabel?.text = "Population: \(country.population) people"
default:
return cell
}
return cell
case currency:
let cell = tableView.dequeueReusableCell(withIdentifier: "Text", for: indexPath)
cell.textLabel?.numberOfLines = 0
switch indexPath.row {
case 0:
// How to represent each currency of country?
cell.textLabel?.text = "Currency name: \(currencyName)"
case 1:
cell.textLabel?.text = "Currency symbol: \(currencySymbol)"
default:
return cell
}
return cell
default:
break
}
return UITableViewCell ()
}
func getCurrencyName () {
for currency in currencies! {
currencyName = currency.name ?? ""
currencySymbol = currency.symbol ?? ""
}
}
}
For now I understand how to represent one currency of each country, but how I can represent all currencies of each country in different rows?
Sorry for my English it's not my native language :)
I would suggest getting a sorted list of the currencies. E.g., for a given Country:
let currencies = country.currencies?.sorted { $0.0 < $1.0 }
To get the count:
let count = currencies?.count ?? 0
To get the details for a particular row, it would be:
if let (code, currency) = currencies?[indexPath.row] {
let currencyCode = code
let currencyName = currency.name
let currencySymbol = currency.symbol
}
You can access the number of currencies for each country with this, and use it in the numberOfRowsInSection method to return enough number of rows for currencies:
country.currencies.count
The rest is filling the cells in the cellForRowAt method by using the indexPath's section and row values. You should iterate over the currencies dictionary of the selected country, and display each key and value pair in the dictionary in a row.
You can do the iteration like so:
for (key, value) in dict {
// Use key and value here
}

Table View sections for fetchedResultsController objects

I'm trying to index my tableView data, ordering the names of the people listed by its name initials. I've seen lots of tutorials about doing so with genericall non-persistent data, but none with Core Data and making use of fetchedResultsController option (for iCloud auto-sync).
Now my code is as follows (for clarity purposes, I transcribe only question-related parts):
//The two arrays to populate from Core Data, used later to populate the table
var alumnosDictionaryArray = [String:[String]]()
var titulosSeccionArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
//Obtain data from Core Data
updateFetchedResultsController()
//Populate the arrays with this data
populateArrays()
func updateFetchedResultsController() {
guard let context = container?.viewContext else { return }
context.performAndWait {
let sortDescriptor = NSSortDescriptor(key: "nombre", ascending: true)
request.sortDescriptors = [sortDescriptor]
fetchedResultsController = NSFetchedResultsController<Alumno>(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
do {
try fetchedResultsController?.performFetch()
tableView.reloadData()
} catch {
print("Error obtaining data: \(error)")
}
}
}
func populateArrays() {
if let objects = fetchedResultsController?.fetchedObjects {
for alumno in objects {
let inicial = String(alumno.nombre?.prefix(1) ?? "")
if var values = alumnosDictionaryArray[inicial] {
values.append(alumno.nombre!)
alumnosDictionaryArray[inicial] = values
} else {
alumnosDictionaryArray[inicial] = [alumno.nombre!]
}
}
titulosSeccionArray = [String](alumnosDictionaryArray.keys)
titulosSeccionArray = titulosSeccionArray.sorted(by: {$0 < $1})
}
}
This part seems to work ok, as the arrays are filled correctly, as I have checked with printing statements.
Later, for table data:
override func numberOfSections(in tableView: UITableView) -> Int {
return titulosSeccionArray.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let inicial = titulosSeccionArray[section]
if let values = alumnosDictionaryArray[inicial] {
return values.count
}
return 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! AlumnoCell
if self.validateIndexPath(indexPath) {
let object = fetchedResultsController?.object(at: indexPath)
let inicial = titulosSeccionArray[indexPath.section]
if let values = alumnosDictionaryArray[inicial] {
cell.nombreField.text = values[indexPath.section]
cell.especialidadField.text = object?.especialidadRelacionada?.nombre
cell.cursoField.text = object?.cursoRelacionado?.nivel
cell.tutorField.text = object?.tutorRelacionado?.nombre
}
} else {
print("Error from indexPath")
}
return cell
}
//To validate indexPath
func validateIndexPath(_ indexPath: IndexPath) -> Bool {
if let sections = self.fetchedResultsController?.sections,
indexPath.section < sections.count {
if indexPath.row < sections[indexPath.section].numberOfObjects {
return true
}
}
return false
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return titulosSeccionArray[section]
}
I get a more-or-less complete table with the correct sections, title of sections (initials), and number of rows for each, but it seems that only firs row works, as the rest of data are empty and throw an error when selecting any: 'no section at index 1'. In fact, I get many "Error from indexPath" messages on the console before exception throwing...
Any suggestion? Thanks.
For Alumno entity add 1 more column of nombreFirstChar.
Then use something like this to save this variable in database:
alumno.nombreFirstChar = nombre.firstCharacter()
Here firstCharacter helper method will be like this:
func firstCharacter() -> String {
var firstCharacter = self.first ?? "#"
if (!(firstCharacter >= "a" && firstCharacter <= "z") && !(firstCharacter >= "A" && firstCharacter <= "Z") ) {
firstCharacter = "#"
}
return String(firstCharacter).capitalized
}
Now in your fetchedResultsController,replace this line
fetchedResultsController = NSFetchedResultsController<Alumno>(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
with
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "nombreFirstChar", ascending: true)]
fetchRequest.propertiesToGroupBy = ["nombreFirstChar"]
fetchedResultsController = NSFetchedResultsController<Alumno>(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nombreFirstChar, cacheName: nil)
Then in table view delegate apis, you can use fetchedResultsController like this:
func numberOfSections(in tableView: UITableView) -> Int {
return fetchedResultsController.sections?.count ?? 0
}
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
guard let fetchedResultsSections: [NSFetchedResultsSectionInfo] = channelFetchedResultsController.sections else {return nil}
return fetchedResultsSections!.map{String($0.name)}
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
guard var fetchedResultsSections: [NSFetchedResultsSectionInfo] = fetchedResultsController.sections else {return nil}
return fetchedResultsSections[section].name
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard var sections:[NSFetchedResultsSectionInfo] = fetchedResultsController.sections, let section = sections[section] else {
return 0
}
return section.numberOfObjects
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
cell.accessoryType = .disclosureIndicator
let alumno = fetchedResultsController.object(at: indexPath)
...
return cell
}

How to use Firestore object data to populate tableview

With the help of some videos I was able to parse my Firestore data into an array of arrays, but now I'm having a bit of trouble figuring out how to use this data to populate my tableview.
The idea is to use the 'dow' field for the Sections and the 'workouts' field for the Rows.
If I print the first index of my array, this is the output that I'm getting...
[Days(dow: "Tuesday", workouts: [Effortflex.Workouts(workout: "Back")]), ..., ...]
I feel like I'm missing a step here. Can someone assist me with this please.
self.loadData { (Bool) in
if Bool == true {
print(self.dataArray[0])
self.dayCount = self.dataArray.count
}
}
struct Workouts {
var workout : String
}
struct Days {
var dow : String
var workouts : [Workouts]
var dictionary: [String : Any] {
return ["dow" : dow]
}
}
extension Days {
init?(dictionary: [String : Any], workouts : [Workouts]) {
guard let dow = dictionary["dow"] as? String else { return nil }
self.init(dow: dow, workouts: workouts)
}
}
//MARK: - Load Data
func loadData(completion: #escaping (Bool) -> ()){
let group = DispatchGroup()
self.rootCollection.getDocuments (completion: { (snapshot, err) in
if let err = err
{
print("Error getting documents: \(err.localizedDescription)");
}
else {
guard let dayDocument = snapshot?.documents else { return }
for day in dayDocument {
group.enter()
self.rootCollection.document(day.documentID).collection("Workouts").getDocuments { (snapshot, err) in
var workouts = [Workouts]()
guard let workoutDocument = snapshot?.documents else { return }
for workout in workoutDocument {
let workoutString = workout.data()["workout"] as! String
let newWorkout = Workouts(workout: workoutString)
workouts.append(newWorkout)
}
let dayTitle = day.data()["dow"] as! String
let newDay = Days(dow: dayTitle, workouts: workouts)
self.dataArray.append(newDay)
group.leave()
}
}
}
group.notify(queue: .main){
completion(true)
}
})
}
I made an example that you can use as a basis to be able to use your structure.
Make your array into a list and persist in the scope of your class so that the methods of the tableView can access it. You can transform as follows:
let dataList = dataArray.flatMap{$0}
Then use the data to display on the tableView
func numberOfSections(in tableView: UITableView) -> Int {
dataList.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataList[section].workouts.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return dataList[section].dow
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "testcell", for: indexPath)
cell.textLabel?.text = dataList[indexPath.section].workouts[indexPath.row].workout
return cell
}

Grouped sections not displaying in tableview

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

Tableview not reloading data

I am stuck on this minor issue, I have a tableviewcontroller which is also searchresultcontroller. I am getting correct data against each api call but tableview is not reloading. I have no clue why its not working. Any help or lead will be very much appreciated.
class MasterViewController: UITableViewController,UISearchResultsUpdating {
var request:DataRequest?
var peopleArr:[Peoples] = []
// MARK: - View Setup
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Search"
definesPresentationContext = true
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50.0
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.section == 1 {
// if searchController.searchBar.selectedScopeButtonIndex == 0 {
let profileVc = self.storyboard?.instantiateViewController(withIdentifier: "profileVc") as! ProfileController
profileVc.profileData = (peopleArr[indexPath.row].user_id, peopleArr[indexPath.row].user_id)
self.navigationController?.pushViewController(profileVc, animated: true)
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return peopleArr.count
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return tableView.dequeueReusableCell(withIdentifier: "headerPeopleSec")
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "People"
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FollowingsCell", for: indexPath) as! FollowingsCell
cell.textLabel?.text = "\(indexPath.row)"
let people: Peoples
people = peopleArr[indexPath.row]
if people.following == "1" {
cell.followBtn.isHidden = true
}
else{
cell.followBtn.isHidden = false
}
cell.profile_thumb!.showImageWithURL(urlString: people.photo_url)
cell.addAction = { cell in
self.addFriendAction(indexPath: indexPath , user:people)
}
cell.profile_thumb.motionIdentifier = people.user_id
cell.username.text = people.user_name
return cell
}
func getPeopleList(searchString:String?) {
if let req = self.request {
req.cancel()
}
let peopleBag = [
"auth_token": (MemberProfile.loggedUser?._auth_token())!,
"per_page": 30,
"page": 1,
"search_key": searchString ?? ""
] as [String : Any]
NVActivityIndicatorPresenter.sharedInstance.startAnimating(activityData)
self.request = HelperClass().doGetRequestCustom(url: BASE_URL + SEARCH_PEOPLE, param:peopleBag, header: [:], completion: {(response,responseObject, error) in
if let resMsg = (responseObject?.message.resp_status) {
NVActivityIndicatorPresenter.sharedInstance.stopAnimating()
// if let hasNext = responseObject?.message.paging_data.next_page_exist as? Bool {
// self.hasNextPage = hasNext
// }
let dictionary:[String: AnyObject]? = responseObject?.message.data as? [String:AnyObject] //["member_followings"]
if let dict:Array = dictionary?["member_profiles"] as? Array<[String:AnyObject]>{
for dic in dict {
let friend = Peoples()
friend.photo_url = (dic["photo"] as? String) ?? ""
friend.user_name = ((dic["user"]?["username"])! as String)
friend.user_id = (dic["id"])! as! String
friend.following = (dic["is_following"])! as! String
self.peopleArr.append(friend)
}
self.tableView.reloadData()
}
else{
}
}
else{
NVActivityIndicatorPresenter.sharedInstance.stopAnimating()
}
NVActivityIndicatorPresenter.sharedInstance.stopAnimating()
})
}
func addFriendAction(indexPath:IndexPath , user:Peoples) {
let followBag = [
"auth_token": (MemberProfile.loggedUser?.auth_token)!,
"following_profile_id": user.user_id
] as [String : Any]
NVActivityIndicatorPresenter.sharedInstance.startAnimating(activityData)
HelperClass().doPostRequest(url: BASE_URL+FOLLOW_MEMBER , param: followBag, completion: { (dataResponse,response,error) in
if (response != nil) && (response?.message.resp_status)!
{
NVActivityIndicatorPresenter.sharedInstance.stopAnimating()
let cell = self.tableView.cellForRow(at: indexPath) as! FollowingsCell
cell.followBtn.isHidden = true
user.following = "1"
}
else
{
if (response != nil){
NVActivityIndicatorPresenter.sharedInstance.stopAnimating()
HelperClass.showAlertViewWithTitle(title: "Error", Text: (response?.message.message)!, controllerToShowOn: self)
}
else{
NVActivityIndicatorPresenter.sharedInstance.stopAnimating()
HelperClass.showAlertViewWithTitle(title: "Error", Text: "Something went wrong. Please check your internet connection & try again later.", controllerToShowOn: self)
}
return
}
})
}
func updateSearchResults(for searchController: UISearchController) {
if !(searchController.searchBar.text! == "") {
self.peopleArr.removeAll()
self.tableView.reloadData()
let searchBar = searchController.searchBar
self.getPeopleList(searchString: searchBar.text!)
}
}
}
You need to make your reload call on the main thread:
...
for dic in dict {
let friend = Peoples()
friend.photo_url = (dic["photo"] as? String) ?? ""
friend.user_name = ((dic["user"]?["username"])! as String)
friend.user_id = (dic["id"])! as! String
friend.following = (dic["is_following"])! as! String
self.peopleArr.append(friend)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
...
All UI modification always has to take place on the main thread. Most of the time you're in a completion handler you'll have to dispatch to main to modify the UI.
Seems something wrong in func updateSearchResults(for searchController: UISearchController).
Can you try moving self.tableView.reloadData() at the end of this function ?
It seems when reloadData is called, the array as cleared, and not yet populated with new values.

Resources