Populating Cells in a tableView from API Call - ios

I am forced to use an asynchronous call (I guess closure in swift) to get Data I need using an SDK (APIWrapper). I'm finding that the view is being initalized before I am able to get the data that I need.
So my 1st question to y'all is, how can I get my cells to bring in the data that I need to the table view before the view loads? Then, why would I want to use an asyncronous call at this point
import APIWrapper
import UIKit
class MyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let provider = APIWrapper
var categories = [String]()
//define number of cells
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
categories = []
self.getCells()
print("count " , self.categories.count)
return(self.categories.count)
}
//get number of cells
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "categories")
cell.textLabel?.text = categories[indexPath.row]
return(cell)
}
private func getCells(){
provider?.getCategoriesWithCallback { (response, error) -> () in
if error == nil {
print("response ", response)
self.updateTableViewWithCategories(categories: response as! [APIWrapperCategory])
}
else {
print("FUCKK")
}
}
}
private func updateTableViewWithCategories(categories: [APIWrapperCategory]){
for category in categories{
print("category obj " , category)
print("category name " , category.name)
}
}
}
The output from my console looks like
count 0
count 0
count 0
count 0
response Optional([<APIWrapperCategory: 0x6000002a0300>])
category obj <ZDKHelpCenterCategory: 0x6000002a0300>
category name General
response Optional([<ZDKHelpCenterCategory: 0x6180002a30c0>])
category obj <ZDKHelpCenterCategory: 0x6180002a30c0>
category name General
response Optional([<ZDKHelpCenterCategory: 0x6180002a30c0>])
category obj <ZDKHelpCenterCategory: 0x6180002a30c0>
category name General
response Optional([<ZDKHelpCenterCategory: 0x6180002a3300>])
category obj <ZDKHelpCenterCategory: 0x6180002a3300>
category name General

You are getting data for your table view from the data source method of the tableview.
To get data from an API call, call self.getCells() method in viewDidLoad() method of your view controller like this:
override func viewDidLoad() {
//your code here
//get cells data
self.getCells()
}
And add your api response data to table view data source as:
private func updateTableViewWithCategories(categories: [APIWrapperCategory]){
self. categories = []
for category in categories{
print("category obj " , category)
print("category name " , category.name)
self. categories.append(category.name)
}
//reload table view here
DispatchQueue.main.async {
self.yourTableView.reloadData()
}
}
and change the delegate method as:
//define number of cells
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("count " , self.categories.count)
return(self.categories.count)
}

So I ended up using viewWillAppear and changed a few things on the way data is returned to make the cells populate so here's the code, I hope this can help someone else out
#IBOutlet weak var tableView: UITableView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.getCategoriesFromZendesk()
}
//define number of cells
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("count " , self.categories.count)
return self.categories.count
}
//get number of cells
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//Add Label to the Prototype Cell
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "categories")
cell.textLabel?.text = categories[indexPath.row]
return(cell)
}
private func getCategories(){
self.categories = []
provider?.getCategoriesWithCallback { (response, error) -> () in
if error == nil {
print("response ", response?.map{($0 as AnyObject as? APIWrapperCategory)?.name ?? ""} ?? "empty")
self.updateTableViewWithCategories(categories: response as? [APIWrapperCategory])
}
else {
print("FUCKK")
}
}
}
private func updateTableViewWithCategories(categories: [APIWrapperCategory]?){
self.categories = categories?.flatMap{$0.name} ?? []
tableView.reloadData()
}

Related

Filtering query for Realm

I have a function which prints all the objects in my realm table to a table view. I would like to be able to filter these objects by their "muscle" property.
Here's my DB helper functions:
func getMusclesCount()-> Int {
let storedExercise = realm.objects(StoredExercise.self)
return storedExercise.count
}
//MARK:- getAllMuscelsNames
func getAllMusclesNames()-> [String] {
var musclesName = [String]()
let storedExercise = realm.objects(StoredExercise.self)
for exercise in storedExercise {
print("Muscle = \(exercise.muscle)")
musclesName.append(exercise.name)
}
return musclesName
}
Here's my Table View Controller class :
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return DBHelper.shared.getAllMusclesNames().count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
}
let muscle = DBHelper.shared.getAllMusclesNames()[indexPath.row]
cell.textLabel?.text = muscle
return cell
}
I've tried adding .Filter to 'let storedExercise' but I'm not sure how to set it up correctly. Any assitance would be greatly appreciated, thanks.
If your StoredExercise model looks like this
class StoredExercise: Object {
#objc dynamic var muscle = ""
}
then to get all of the exercises that are for the biceps, it's this
let bicepResults = realm.objects(StoredExercise.self).filter("muscle == 'biceps'")

Json request retrieving results but only displaying the first result

I have a tableview which is getting data from an api request, from that request its only displaying the first result.
//MARK:-- Computed vars
var listOfLodges = [LodgeDetail](){
didSet{
DispatchQueue.main.async {
self.tableView.reloadData()
self.navigationItem.title = "\(self.listOfLodges.count) lodges found"
print("\(self.listOfLodges.count) lodges found") // returns 11 lodges
print(" ")
}
}
}
this displays 11 returned results, all the data is different.
//MARK: -- ViewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
//MARK:-- Setting up Table view
#objc func setDelegate(){
let landing = LandingViewController()
landing.delegate = self
}
override func numberOfSections(in tableView: UITableView) -> Int {
return listOfLodges.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
this is probably where my errors are, I am unsure of what could be going wrong?
// possible errors here
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for:indexPath)
let lodge = listOfLodges[indexPath.row]
let rating: String = String(lodge.rating!)//careful force unwrapping
print(rating) // prints the same 4.2
print(lodge.name) //prints the same name
return cell
}
}
//MARK:-- Functions called on did load
extension ListViewController{
func loadData(){
let lodgeRequest = LodgeRequest(lat:passedLat, long:passedLong)
lodgeRequest.getLodges{[weak self] result in
switch result{
case.failure(let error):
print(error)
case.success(let lodges):
self?.listOfLodges = lodges
}
}
}
}
Change
let lodge = listOfLodges[indexPath.row]
to
let lodge = listOfLodges[indexPath.section]
As you only have one row per section then indexPath.row is always 0 so first item is displayed for all sections

showing an error as has need to conform the protocol

This is my code:-
Model:-
class QuestionListModel: NSObject {
var optionsModelArray:[OptionsModel] = []
var question:String!
init(dictionary :JSONDictionary) {
guard let question = dictionary["question"] as? String
else {
return
}
if let options = dictionary["options"] as? [String]{
print(options)
print(options)
for values in options{
print(values)
let optionmodel = NH_OptionsModel(values: values)
self.optionsModelArray.append(optionmodel)
}
}
self.question = question
// print(self.dataListArray33)
}
}
optionModel:-
class OptionsModel: NSObject {
var values:String?
init(values:String) {
self.values = values
print( self.values)
}
}
in viewmodel:-
var questionsModelArray:Array<NH_QuestionListModel>? = []
init(withdatasource newDatasourceModel:NH_QuestionDataSourceModel) {
datasourceModel = newDatasourceModel
print(datasourceModel.dataListArray?.count)
self.questionsModelArray = datasourceModel.dataListArray
print(self.questionsModelArray)
print(datasourceModel.dataListArray)
}
func numberOfSections() -> Int{
return (self.questionsModelArray?.count)!
}
func titleForHeaderInSection(atindexPath indexPath: IndexPath) -> QuestionListModel {
return self.questionsModelArray![indexPath.row]
}
func numberOfRowsInSection(indexPath:IndexPath) -> Int {
if let questionModel = self.questionsModelArray?[indexPath.section]{
return questionModel.optionsModelArray.count
}
else{
return 0
}
}
func datafordisplay(atindex indexPath: IndexPath) -> OptionsModel{
let questionModel = self.questionsModelArray?[indexPath.section]
return questionModel!.optionsModelArray[indexPath.row]
}
And in ViewController:-
func numberOfSections(in tableView: UITableView) -> Int {
return questionViewModel.numberOfSections()
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: IndexPath) -> UIView? {
// let headercell = Bundle.main.loadNibNamed("HeaderCell", owner: self, options: nil)?.first as! NH_questionheader
let identifier = "HeaderCell"
var headercell: NH_questionheader! = tableView.dequeueReusableCell(withIdentifier: identifier) as? NH_questionheader
if headercell == nil {
tableView.register(UINib(nibName: "NH_questionheader", bundle: nil), forCellReuseIdentifier: identifier)
headercell = tableView.dequeueReusableCell(withIdentifier: identifier) as? NH_questionheader
}
headercell.setReviewData(reviews:questionViewModel.titleForHeaderInSection(atindexPath:section))
return headercell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 150
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: IndexPath) -> Int {
return questionViewModel.numberOfRowsInSection(indexPath: section)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "Cell"
var cell: CellTableViewCell! = tableView.dequeueReusableCell(withIdentifier: identifier) as? CellTableViewCell
if cell == nil {
tableView.register(UINib(nibName: "CellTableViewCell", bundle: nil), forCellReuseIdentifier: identifier)
cell = tableView.dequeueReusableCell(withIdentifier: identifier) as? CellTableViewCell
}
cell.contentView.backgroundColor = UIColor.clear
cell.setOptions(Options1: questionViewModel.datafordisplay(atindex: indexPath))
print("Section \(indexPath.section), Row : \(indexPath.row)")
return cell
}
my json file:-
{
"data":[
{
"question": "Gender",
"options": ["Male","Female"]
},
{
"question": "How old are you",
"options": ["Under 18","Age 18 to 24","Age 25 to 40","Age 41 to 60","Above 60"]
}, {
"question": "I am filling the Questionnaire for?",
"options": ["Myself","Mychild","Partner","Others"]
}
]
}
This is my data .So i need to display the questions in header and options in the cell for index .But showing as error as UITableview has need to conform the protocol UITableviewDataSource.
Also showing error as Index out of range.
How to do.....
I think you are not assign a datasource to your view controller. So please assign it in your ViewDidLoad of your view controller
override func viewDidLoad() {
super.viewDidLoad()
self.yourtableview.delegate = self
self.yourtableview.dataSource = self
// Do any additional setup after loading the view.
}
This error usually occurs when you fail to implement the required methods of a protocol. In this case the methods would be :
cellForRowAt
numberOfRowsInSection
Since you already have them implemented in your view controller chances are that you might have failed to set the datasource for the table view.
Refer to this
https://developer.apple.com/documentation/uikit/uitableviewdatasource
your view controller cannot find the data source and delegate for the table view. make sure you have assigned the data source and delegate
self.yourtableview.delegate = self
self.yourtableview.dataSource = self
and also make sure that your controller also inherit the UITableViewDelegate and UITableViewDataSource like this
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource
To achieve what you want, you should set your VC as the delegate and datasource of your table.
Option 1, do it dynamically:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
}
Option 2, from your storyboard (example below):
After this, you should use the following datasource functions of UITableView:
// return number of questions
func numberOfSections(in tableView: UITableView) -> Int
// return number of options per question (indicated by section)
func tableView(UITableView, numberOfRowsInSection: Int) -> Int
You haven't correctly declared the numberOfRowsInSection function; section is an Int, not an IndexPath. As a result you have not implemented the mandatory functions of UITableViewDataSource.
You want:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return questionViewModel.numberOfRowsIn(section: section)
}
With an appropriate change in your view model:
func numberOfRowsIn(section:Int) -> Int {
return self.questionsModelArray?[section].optionsModelArray.count ?? 0
}
I would also suggest that you review your use of implicitly unwrapped optionals and force unwrapping; this is just asking for crashes.
For example, there is no reason for the question property of QuestionListModel to be String!; just declare it as String and make your initialiser failable. Better yet, use Codable to create your model from JSON and get rid of all of that code.
You can eliminate the force unwrapping in numberOfSections too:
func numberOfSections() -> Int {
return self.questionsModelArray?.count ?? 0
}
I would also suggest you make QuestionListModel a struct rather than an NSObject subclass.
If I were you I would re-factor to remove the view model, it is adding unnecessary complexity in this case, and use Codable for your JSON deserialisation:
struct Questions: Codable {
enum CodingKeys: String, CodingKey {
case questions = "data"
}
var questions: [Question]
}
struct Question: Codable {
var question: String
var options: [String]
}
Your view controller then becomes much simpler:
class ViewController: UIViewController, UITableViewDatasource {
var questionData: Questions?
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "NH_questionheader", bundle: nil), forCellReuseIdentifier: "HeaderCell")
tableView.register(UINib(nibName: "CellTableViewCell", bundle: nil), forCellReuseIdentifier: "Cell")
// You don't show how you load your JSON, but assuming you have it in an instance of `Data` called `jsonData`:
do {
self.questionData = try JSONDecoder().decode(Questions.self, from: jsonData)
} catch {
print("Error decoding JSON: \(error.localizedDescription)")
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: IndexPath) -> UIView? {
let identifier = "HeaderCell"
guard let questionData = self.questionData,
let headercell = tableView.dequeueReusableCell(withIdentifier: identifier) as? NH_questionheader else {
return nil
}
headercell.label.text = questionData.questions[section].question
return headercell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 150
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.questionData?.questions[section].options.count ?? 0
}
func numberOfSections(in tableView: UITableView) -> Int {
return self.questionData?.questions.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "Cell"
// Note, I have used force unwrapping and a forced downcast here as if either of these lines fail you have a serious problem and crashing is the simplest way of finding it during development
let option = self.questionData!.questions[indexPath.section].options[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath ) as! CellTableViewCell
cell.contentView.backgroundColor = .clear
cell.label.text = option
return cell
}
}
Once you have this basic approach working you can try and add a view model if you like.

UITableView not being populated

I have a method which grabs JSON and appends individual values from the JSON into the array like so:
internal let methods = Methods()
var countryData = [""]
#IBOutlet weak var countryTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
displayCountries()
}
func displayCountries() {
// Wait for task to complete before grabbing JSON
methods.getCountriesData {() -> () in
for (_, value) in self.methods.getJSON() {
self.countryData.append(String(describing: value))
}
DispatchQueue.main.async {
self.countryTableView.reloadData()
print(self.countryData)
}
}
}
I have UITableView delegates declared like so also:
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return countryData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")! as UITableViewCell
let row = indexPath.row
cell.textLabel?.text = self.countryData[row]
return cell
}
The print(self.countryData) prints all of the countries within the log (100's of countries) however for some reason the countries aren't being displayed in the UITableView, does anyone understand why?
You need to specify the table view's data source. This can be done in code or in the storyboard.
In code:
countryTableView.dataSource = self
You will also need to have your data source object conform to the UITableViewDataSource protocol:
MyViewController: UIViewController, UITableViewDataSource {
Since the data is an array of strings and a property on the view controller, this should work fine.
In the storyboard:
Simply drag the dataSource outlet to the object which is providing the data (in this example it is the view controller):

Ambiguous reference to member '(_:numberOfRowsInSection:)'

I'm trying to GET gists from Github and pop them in a table view,
here's the entire code, Gist is a class defined elsewhere:
var gists = [Gist]()
override func viewDidAppear(animated: Bool) {
loadGists()
}
func loadGists() {
GithubAPIManager.sharedInstance.fetchPublicGists() { result in
guard result.error == nil else {
print("Error 1")
return
}
if let fetchedGists = result.value {
self.gists = fetchedGists
}
self.tableView.reloadData()
//Error here.
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return gists.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell")!
let gist = gists[indexPath.row]
cell.textLabel?.text = gist.description
cell.detailTextLabel?.text = gist.ownerLogin
return cell
}
So, the problem is I didn't add an outlet of the table view to the View Controller.swift.
Just dragged the table view to the .swift file to create it.

Resources