TableView being assigned to navigation controller not view controller - ios

I have an app that is playing up. All my data is being displayed in the black circle.
I need it to be displayed in the red circle. Is it a problem in my controller class or do I need to somehow assign the tableview to this controller? I couldn't find any solutions like this online
image1
This is my code for the ViewController class:
import UIKit
import SafariServices
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
private
let tableView: UITableView = {
let table = UITableView()
table.register(NewsTableViewCell.self,
forCellReuseIdentifier: NewsTableViewCell.identifier)
return table
}()
private
var viewModels = [NewsTableViewCellViewModel]()
private
var articles = [Article]()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.hidesBackButton = true
title = "News"
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
// Do any additional setup after loading the view
getTopArticles()
searchArticles()
}
func getTopArticles() {
ArticlesAPI.shared.getTopArticles {
[weak self] result in //looking at API
switch result {
case.success(let articles): // if there are results
self?.articles = articles
self?.viewModels = articles.compactMap({
NewsTableViewCellViewModel(title: $0.title, //tableview needs to be added
subtitle: $0.description ?? "No Description",
imageURL: URL(string: $0.urlToImage ?? ""))
})
// break
DispatchQueue.main.async {
self?.tableView.reloadData()
}
case.failure(let error): //prints error if there is one
print(error)
}
}
}
func searchArticles() {
ArticlesAPI.shared.searchArticles(with: "apple") {
[weak self] result in //looking at API
switch result {
case.success(let articles): // if there are results
self?.viewModels = articles.compactMap({
NewsTableViewCellViewModel(title: $0.title, //tableview needs to be added
subtitle: $0.description ?? "No Description",
imageURL: URL(string: $0.urlToImage ?? ""))
})
//break
DispatchQueue.main.async {
self?.tableView.reloadData()
}
case.failure(let error): //prints error if there is one
print(error)
}
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.frame = view.bounds
}
// table
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModels.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard
let cell = tableView.dequeueReusableCell(withIdentifier: NewsTableViewCell.identifier,
for: indexPath)
as ? NewsTableViewCell
else {
fatalError()
}
cell.configure(with: viewModels[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let viewModel = viewModels[indexPath.row]
let article = articles[indexPath.row]
guard
let url = URL(string: article.url ?? "")
else {
return
}
let vc = SFSafariViewController(url: url)
present(vc, animated: true)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 150
}
}

The red controller is embedded inside the black controller. The black controller is a navigation controller which provides the navigation bar for the red controller. You can type navigationController inside of your view controller and you will see that it is of type UINavigationController?, and Optional (?) since it may or may not exist (in your case it does exist because you embedded your controller in the storyboard). So don’t worry, your content is already inside the red view controller.
The reason why you think it isn’t, is because you have created your tableView programatically and you gave it the size of the whole screen, so it covers up all of the other content:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.frame = view.bounds
}
You can verify this using the layout inspector.
This isn’t needed because you already have a UITableView in your storyboard, you just need to create a reference to it in your view controller. So instead of:
private
let tableView: UITableView = {
let table = UITableView()
table.register(NewsTableViewCell.self, forCellReuseIdentifier: NewsTableViewCell.identifier)
return table
}()
you need:
#IBOutlet private var tableView: UITableView!
and you can register your cell alongside the other tableView setup code inside the viewDidLoad method:
// view.addSubview(tableView) -> autoLayout does this automatically, hence the name autoLayout
tableView.register(NewsTableViewCell.self, forCellReuseIdentifier: NewsTableViewCell.identifier)
tableView.delegate = self
tableView.dataSource = self
And remove the code in your viewDidLayoutSubviews method as well, so that the tableView doesn’t take up all of the space.

Related

Swift: How to Load JSON in different Table View

Hi everybody I'm new to Swift and I need help.
So I have three different JSON that will show in different moments, The first JSON has been loading perfectly, but when I clicking on the item to reload another JSON and show the detail nothing appears.
I'm confused about details:
Need I have three differents table Views for each JSON? or the only one is enough?
When I working with data (JSON) need I use a specific function to prepare the new JSON that will appear as "prepare"?
In my project I have:
Two view controllers: ViewController(default) and DetailViewController.
In my Main.Storyboard: Tab Bar Controller --> Navigation --> Table View
The code of the first View controller:
import UIKit
class ViewController: UITableViewController {
var categories = [Category]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let urlString = "https://www.themealdb.com/api/json/v1/1/categories.php"
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url) {
parse(json: data)
} else {
print("error connecting")
}
}
}
func parse(json: Data) {
let decoder = JSONDecoder()
print("parse called")
do {
let jsonCategories = try decoder.decode(Categories.self, from: json)
categories = jsonCategories.categories
tableView.reloadData()
} catch {
print("error parsin: \(error)")
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categories.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let category = categories[indexPath.row]
cell.textLabel?.text = category.idCategory
cell.detailTextLabel?.text = category.strCategoryDescription
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let newViewController = DetailViewController()
self.navigationController?.pushViewController(newViewController, animated: true)
}
}
The code of the second view controller:
import UIKit
import WebKit
class DetailViewController: UIViewController {
var webView: WKWebView!
var meals = [Meal]()
override func loadView() {
webView = WKWebView()
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://www.themealdb.com/api/json/v1/1/filter.php?c=Beef"
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url) {
parse(json: data)
} else {
print("error connecting")
}
}
}
func parse(json: Data) {
let decoder = JSONDecoder()
print("parse called")
do {
let jsonMeals = try decoder.decode(Meals.self, from: json)
meals = jsonMeals.meals
print(String(format:"read %d meals", meals.count))
} catch {
print("error parsin: \(error)")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return meals.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let meal = meals[indexPath.row]
cell.textLabel?.text = meal.idMeal
cell.detailTextLabel?.text = meal.strMeal
return cell
}
}
The function parse(json: Data) on DetailViewController does not call tableView.reloadData() so the new data cannot be loaded to UITableView
Need I have three differents table Views for each JSON? or the only one is enough?
If you're loading different JSON files but want to show in the same view. You only need 1 UITableViewCell.
When you load & decode JSON, consider moving it to background queue to avoid blocking main thread.

Data from firebase not populating in my table view

So I recently asked a question regarding firebase - but have refactored my code quite a bit as I wanted all of my firebase methods to be done in the same place. I am having the following issue after the refactor...
Data from my firebase database is not populating my tableview. I'm not too sure why this would be, as it was working fine before I moved the method to a separate file from my table view(for cleaner code). All I did was move the method that populates the array to a separate file, return an array and then reload the tableview after calling the method. Below is the code in question:
In my FireBaseMethods class
//-------------- POPULATE TABLE ARRAY -----------------//
public func populateConsumableTableArray() -> [Consumable]{
var tableArray = [Consumable]()
//let the object populate itself.
ref.child("Consumables").observe(.childAdded, with: { snapshot in
let dataChange = snapshot.value as? [String:AnyObject]
let aRequest = Consumable(aDict: dataChange!)
tableArray.append(aRequest)
})
return tableArray
}
In my ListOfConsumablesViewController table view class
class ListOfConsumablesViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
private var methods:MethodsForController = MethodsForController()
private var fireBaseMethods:FireBaseMethods = FireBaseMethods()
private var consumableArray = [Consumable]()
let picker = UIImagePickerController()
#IBOutlet weak var consumableTable: UITableView!
//-------------------- VIEW DID LOAD -----------------------//
override func viewDidLoad() {
super.viewDidLoad()
//Trying to populate the table view here...
consumableArray = fireBaseMethods.populateConsumableTableArray()
consumableTable.reloadData()
self.consumableTable.dataSource = self
self.consumableTable.delegate = self
}
...
//---------------------- FUNCTIONS FOR TABLE VIEW CELLS & TABLE ----------------------//
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(consumableArray.count)
return consumableArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "consumableCell", for: indexPath) as! ConsumablesCell
cell.layer.borderWidth = 1
cell.layer.borderColor = UIColor.lightGray.cgColor
cell.backgroundColor = UIColor.white
cell.adapterType.text = consumableArray[indexPath.row].getType()
cell.count.text = String(consumableArray[indexPath.row].getCount())
if Int(consumableArray[indexPath.row].getCount()) ?? 0 <= 0{
cell.count.textColor = UIColor.red
}else{
cell.count.textColor = UIColor.black
}
cell.sku.text = consumableArray[indexPath.row].getSku()
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 90
}
}
As shown below, nothing populates in the table view... I'm assuming it's something to do with the method being in a separate file, but I'm not really sure why that would be?
Simple implementation of a completion handler
//-------------- POPULATE TABLE ARRAY -----------------//
public func populateConsumableTableArray(completion: #escaping (Consumable) -> Void) {
//let the object populate itself.
ref.child("Consumables").observe(.childAdded, with: { snapshot in
guard let dataChange = snapshot.value as? [String:AnyObject] else { return }
let aRequest = Consumable(aDict: dataChange)
completion(aRequest)
})
}
override func viewDidLoad() {
super.viewDidLoad()
self.consumableTable.dataSource = self
self.consumableTable.delegate = self
//Trying to populate the table view here...
fireBaseMethods.populateConsumableTableArray { [unowned self] consumable in
self.tableArray.append(consumable)
DispatchQueue.main.async {
self.consumableTable.reloadData()
}
}
}

My custom cells are not showing up in my tableview

So I have been trying to get my custom cells to show up on this tableview, but I am not sure as to why they are not showing up
I have already checked other stack overflow questions and tried their fixes, to no avail. Please ignore the aws stuff as you can see I have the text hard coded so I can just get them to appear for now.
This is the code within the class holding the tableview
import Foundation
import AWSDynamoDB
import AWSCognitoIdentityProvider
import UIKit
// this will be the main feed class showing the user data
class UserDetailTableViewController : UITableViewController {
// attributes for the custome cell
#IBOutlet weak var testing: UITextField!
#IBOutlet var Table: UITableView!
var response: AWSCognitoIdentityUserGetDetailsResponse?
var user: AWSCognitoIdentityUser?
var pool: AWSCognitoIdentityUserPool?
var questiondata : Array<Phototext> = Array()
override func viewDidLoad() {
tableView.delegate = self
tableView.dataSource = self
super.viewDidLoad()
self.pool = AWSCognitoIdentityUserPool(forKey: AWSCognitoUserPoolsSignInProviderKey)
if (self.user == nil) {
self.user = self.pool?.currentUser()
}
// grabbing data from our aws table
updateData()
self.refresh()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.setToolbarHidden(true, animated: true)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setToolbarHidden(false, animated: true)
}
#IBAction func Questions(_ sender: Any) {
performSegue(withIdentifier: "ask", sender: self)
}
// MARK: - IBActions
#IBAction func signOut(_ sender: AnyObject) {
self.user?.signOut()
self.title = nil
self.response = nil
self.refresh()
}
// reloads the prior view
func refresh() {
self.user?.getDetails().continueOnSuccessWith { (task) ->
AnyObject? in
DispatchQueue.main.async(execute: {
self.response = task.result
self.title = self.user?.username
// saving the user name from the main menu
username123 = self.user?.username! ?? "broken"
})
return nil
}
}
// function that calls to our aws dynamodb to grab data from the
// user
//and re update questions
// the array list
func updateData(){
let scanExpression = AWSDynamoDBScanExpression()
scanExpression.limit = 20
// testing to grabt the table data upon startup
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
dynamoDBObjectMapper.scan(Phototext.self, expression:
scanExpression).continueWith(block: {
(task:AWSTask<AWSDynamoDBPaginatedOutput>!) -> Any? in
if let error = task.error as NSError? {
print("The request failed. Error: \(error)")
} else if let paginatedOutput = task.result {
// passes down an array of object
for Photo in paginatedOutput.items as! [Phototext] {
// loading in the arraylist of objects
// adding the objects to an arraylist
self.questiondata.append(Photo)
}
DispatchQueue.main.async {
//code for updating the UI
}
}
return ()
})
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// returning the number of rows
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:
"Questionpost", for: indexPath) as! QuestionCell
cell.QuestionText.text = "call it"
cell.Subject.text = "a day"
return cell
}
}
}
Here is the code for the QuestionCell class
import UIKit
class QuestionCell: UITableViewCell {
#IBOutlet weak var Subject: UILabel!
#IBOutlet weak var QuestionText: UITextView!
}
The cell class is called QuestionCell and the identifier I left on the cell in the storyboard is Questionpost
Here is a photo of my story board:
I have fixed it by declaring an extension with the proper types.
extension UserDetailTableViewController: UITableViewDataSource,UITableViewDelegate{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// returning the number of rows
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Questionpost", for: indexPath) as! QuestionCell
cell.QuestionText.text = "call it"
cell.Subject.text = "a day"
return cell
}}
good explanation of what's going on, you need to conform to the UITableViewDataSource and UITableViewDelegate when you inbed a tableview.
Redundant conformance of TableView to protocol UITableViewDataSource with Xib Files

Error setting UILabel text in custom UITableViewCell

I am quite new to Swift programming, but I am having trouble setting UILabel text in my UITableView class for individual UITableViewCell instances.
I have created a custom subclass of UITableViewCell called PizzaTableViewCell and a custom UITableView class called PizzaListTableViewController. I am trying to populate the UITableView instance with data from an array, which is being populated from an API call to my node.js server.
I have included my UITableView subclass, custom UITablveViewCell class, the struct for the data, and a link to a screenshot of the Simulator loading what I have done. Any help is greatly appreciated!
I have verified that the data is being put in the array with no issues, as I can print the contents after the call to fetchInventory method. I have been able to set a single textLabel with
cell.textLabel?.text = pizzas[indexPath.row].name
along with an image in the array with:
cell.imageView?.image = pizzas[indexPath.row].image
but I have 2 more labels that I need in each cell which I cannot set. I have checked my IBOutlets and Storyboard identifiers, and they match the code.
class PizzaListTableViewController: UITableViewController {
var pizzas: [Pizza] = []
override func viewDidLoad() {
super.viewDidLoad()
//title you will see on the app screen at the top of the table view
navigationItem.title = "Drink Selection"
tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza")
//tableView.estimatedRowHeight = 134
//tableView.rowHeight = UITableViewAutomaticDimension
fetchInventory { pizzas in
guard pizzas != nil else { return }
self.pizzas = pizzas!
print(self.pizzas)
//self.tableView.reloadData()
//print(self.pizzas)
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
} //end of viewDidLoad
private func fetchInventory(completion: #escaping ([Pizza]?) -> Void) {
Alamofire.request("http://127.0.0.1:4000/inventory", method: .get)
.validate()
.responseJSON { response in
guard response.result.isSuccess else { return completion(nil) }
guard let rawInventory = response.result.value as? [[String: Any]?] else { return completion(nil) }
let inventory = rawInventory.compactMap { pizzaDict -> Pizza? in
var data = pizzaDict!
data["image"] = UIImage(named: pizzaDict!["image"] as! String)
//print(data)
//print("CHECK")
print("Printing each item: ", Pizza(data: data))
//printing all inventory successful
return Pizza(data: data)
}
completion(inventory)
}
}
#IBAction func ordersButtonPressed(_ sender: Any) {
performSegue(withIdentifier: "orders", sender: nil)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//PRINTING ROWS 0 TWICE in console
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("ROWS", pizzas.count)
return self.pizzas.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: PizzaTableViewCell = tableView.dequeueReusableCell(withIdentifier: "Pizza", for: indexPath) as! PizzaTableViewCell
//cell.backgroundColor = Services.baseColor
//cell.pizzaImageView?.image = pizzas[indexPath.row].image
//THESE WORK BUT ARE A STATIC WAY OF SETTING THE CELLS
//CAN ONLY SET THE SELL WITH A SINGLE TEXT LABEL FROM THE DATA ARRAY
cell.imageView?.image = pizzas[indexPath.row].image
cell.textLabel?.text = pizzas[indexPath.row].name
//cell.textLabel?.text = pizzas[indexPath.row].description
//cell.textLabel?.text = "$\(pizzas[indexPath.row].amount)"
// cell.name?.text = pizzas[indexPath.row].name
// cell.imageView?.image = pizzas[indexPath.row].image
// cell.amount?.text = "$\(pizzas[indexPath.row].amount)"
// cell.miscellaneousText?.text = pizzas[indexPath.row].description
//print(cell.name?.text! as Any)
print(cell.imageView as Any)
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.0
} //END OF
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "pizzaSegue", sender: self.pizzas[indexPath.row] as Pizza)
} //END OF override func tableView
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "pizzaSegue" {
guard let vc = segue.destination as? PizzaViewController else { return }
vc.pizza = sender as? Pizza
}
} //END OF override preppare func
}
class PizzaListTableViewController: UITableViewController {
var pizzas: [Pizza] = []
override func viewDidLoad() {
super.viewDidLoad()
//title you will see on the app screen at the top of the table view
navigationItem.title = "Drink Selection"
tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza")
//tableView.estimatedRowHeight = 134
//tableView.rowHeight = UITableViewAutomaticDimension
fetchInventory { pizzas in
guard pizzas != nil else { return }
self.pizzas = pizzas!
print(self.pizzas)
//self.tableView.reloadData()
//print(self.pizzas)
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
} //end of viewDidLoad
private func fetchInventory(completion: #escaping ([Pizza]?) -> Void) {
Alamofire.request("http://127.0.0.1:4000/inventory", method: .get)
.validate()
.responseJSON { response in
guard response.result.isSuccess else { return completion(nil) }
guard let rawInventory = response.result.value as? [[String: Any]?] else { return completion(nil) }
let inventory = rawInventory.compactMap { pizzaDict -> Pizza? in
var data = pizzaDict!
data["image"] = UIImage(named: pizzaDict!["image"] as! String)
//print(data)
//print("CHECK")
print("Printing each item: ", Pizza(data: data))
//printing all inventory successful
return Pizza(data: data)
}
completion(inventory)
}
}
#IBAction func ordersButtonPressed(_ sender: Any) {
performSegue(withIdentifier: "orders", sender: nil)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//PRINTING ROWS 0 TWICE in console
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("ROWS", pizzas.count)
return self.pizzas.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: PizzaTableViewCell = tableView.dequeueReusableCell(withIdentifier: "Pizza", for: indexPath) as! PizzaTableViewCell
//cell.backgroundColor = Services.baseColor
//cell.pizzaImageView?.image = pizzas[indexPath.row].image
//THESE WORK BUT ARE A STATIC WAY OF SETTING THE CELLS
//CAN ONLY SET THE SELL WITH A SINGLE TEXT LABEL FROM THE DATA ARRAY
cell.imageView?.image = pizzas[indexPath.row].image
cell.textLabel?.text = pizzas[indexPath.row].name
//cell.textLabel?.text = pizzas[indexPath.row].description
//cell.textLabel?.text = "$\(pizzas[indexPath.row].amount)"
// cell.name?.text = pizzas[indexPath.row].name
// cell.imageView?.image = pizzas[indexPath.row].image
// cell.amount?.text = "$\(pizzas[indexPath.row].amount)"
// cell.miscellaneousText?.text = pizzas[indexPath.row].description
//print(cell.name?.text! as Any)
print(cell.imageView as Any)
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.0
} //END OF
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "pizzaSegue", sender: self.pizzas[indexPath.row] as Pizza)
} //END OF override func tableView
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "pizzaSegue" {
guard let vc = segue.destination as? PizzaViewController else { return }
vc.pizza = sender as? Pizza
}
} //END OF override preppare func
}
struct Pizza {
let id: String
let name: String
let description: String
let amount: Float
//let amount: String
let image: UIImage
init(data: [String: Any]) {
//print("CHECK:: pizza.swift")
self.id = data["id"] as! String
self.name = data["name"] as! String
// self.amount = data["amount"] as! Float
self.amount = ((data["amount"] as? NSNumber)?.floatValue)!
self.description = data["description"] as! String
self.image = data["image"] as! UIImage
}
}
As noted above, I have been able to print the contents of the data array with beer names, pictures, descriptions and etc. I have tried to print to console
print(cell.name?.text)
after setting
cell.name?.text = pizzas[indexPath.row].name
but it prints nil and this is a problem. I have been stuck with this for about 2 weeks!
IBOutlets screenshot:
I think i found your Problem, let me explain
What you are doing here is you have a custom UITableViewCell defined in the Storyboard in a Controller named "Root View Controller" which is not your PizzaListTableViewController to put it simply
And as you said you have absolutely no issue regarding the IBOutlets
Now when you say
tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza")
In Your PizzaListTableViewController you are not linking it with the UI of the cell rather just the Code (This is only used when there is no xib of the cell)
Now what you can do to solve this
Solution # 1
Move/Copy your UI of the PizzaTableViewCell to PizzaListTableViewController in the storyboard from your "Root View Controller"
Make sure you add a Reuse Identifier in the Attribute Inspector of the cell in the storyboard
remove tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza") this wont give you an error this time as it will automatically get register
Make sure all the IBOutlets are connected
Solution # 2
create a separate Nib (xib) of the cell
and now you have to register the cell here like
tableView.register(UINib(nibName: "PizzaTableViewCell", bundle: Bundle.main), forCellReuseIdentifier: "PizzaCell")
Hope this helps.
Try this
cell.name?.text = ...
cell.amount?.text = ...
cell.miscellaneousText?.text = ...
cell.pizzaImageView?.image = ...
If it still does not work then make sure your cell and your outlets are not null when setting its value. Hope it helps !
There is something definitely strange going on with your setup.
If you try to name the IBOutlets with the same name as the UITableViewCell default property it'll throw an error. The fact that you were able to set those names and build successfully is strange.
From the screenshot above you can see what happens when I attempted to do this.
Make sure your Table View Controller class is set in the storyboard.
Make sure your Table View Cell class is set in the storyboard.
Make sure that all your outlets are properly connected.
Make sure your Table View Cell Identifier is provided in the storyboard.
My Table View Controller Subclass
My Table View Cell Subclass
cell.imageView?.image and cell.textLabel?.text are optional properties of the table view itself. They are not the properties of the custom cell that you designed.
You use tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza") when you have designed a table view cell in XIB. But as you have designed the cell in the storyboard itself you should set the cell reuse identifier and cell class in the storyboard.
I hope this will help you out.

cellForRowAtIndexPath not called after reloadData but numberOfRowsInSection does

In my current project I'm trying to fetch news from a REST-Endpoint and I wanna refresh the UITableView once the network request is finished. So I call reloadData() but unfortunately cellForRowAtIndexPath isn't called however numberOfRowsInSection gets called and returns the correct number of items.
Following my code stripped down to the relevant parts:
class NewsTableViewController: UIViewController {
private var newsItems: Array<NewsItem>!
private var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
newsItems = []
addDummyItem()
prepareTableView()
getNews()
}
func addDummyItem(){
let newsItem = NewsItem()
newsItem.dateString = "12th May"
newsItem.title = "Lorem ipsum"
newsItem.imgUrl = "URL"
newsItem.url = "URL"
newsItem.message = "Lorem ipsum dolor sit amet"
self.newsItems.append(newsItem)
}
func getNews(){
let url = NSURL(string: "http://someurl.com/news")
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: url!)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(urlRequest) { data, response, error in
guard data != nil && error == nil else {
print(error)
return
}
guard let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode == 200 else {
print("status code not 200; \(response)")
return
}
let json = String(data:data!, encoding: NSISOLatin1StringEncoding)
let arr: AnyObject? = json?.parseJSONString
for item in arr as! Array<AnyObject>{
let newsItem = NewsItem()
newsItem.dateString = (item["dateString"] as AnyObject? as? String) ?? ""
newsItem.title = (item["title"] as AnyObject? as? String) ?? ""
newsItem.imgUrl = (item["imgUrl"] as AnyObject? as? String) ?? ""
newsItem.url = (item["url"] as AnyObject? as? String) ?? ""
newsItem.message = (item["message"] as AnyObject? as? String) ?? ""
self.newsItems.append(newsItem)
}
print("Network request finished - call reloadData()")
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
task.resume()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
automaticallyAdjustsScrollViewInsets = false
}
private func prepareTableView() {
tableView = UITableView()
tableView.dataSource = self
tableView.delegate = self
tableView.separatorColor = UIColor.clearColor()
}
}
extension NewsTableViewController : UITableViewDataSource{
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Item count: ", newsItems.count)
return newsItems.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
print("cellForRowAtIndexPath called with indexPathRow: ", indexPath.row)
let cell = tableView.dequeueReusableCellWithIdentifier("ImageCardViewCell") as! NewsImageCardViewCell
let item: NewsItem = newsItems[indexPath.row]
getImageCardView(item.url, btnDate: item.dateString, textTitle: item.title, textDetail: item.message, imageCardView: cell.imageCardView, imageName: "news_1", indexPath: indexPath)
return cell
}
}
extension NewsTableViewController : UITableViewDelegate{
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
}
As you can see: The UITableView is correctly setup by setting the dataSource and delegate to my class. The reloadData() method is called on the main thread. Following the log-output:
Item count: 1
cellForRowAtIndexPath called with indexPathRow: 0
Network request finished - call reloadData()
Item count: 72
Note: At first there's one dummy item my array just to show you that cellForRowAtIndexPath is indeed called but only one time. After triggering reloadData() on numberOfRowsInSection is called but not the cellForRowAtIndexPath method.
I know that there're a lot of similar issues like this and I've checked them all for possible solutions but none were working. I appreciate any help.
Edit
Please note that the dummy view which is added before the network request is visible. So the UITableViews' height is not 0.
Edit #2
Thanks to everyone for your answers. I've found the issue. I've instantiated the ViewController via the Storyboard but I missed to set an IBOutlet. Due that the bounds of my UITableView were 0,0,0,0.
That because your table view did not add to your window, If the table view is not visible, the UIKit will not deal with UI related methods. It's kind of Apple's lazy load.
Change your prepareTableView function as below:
private func prepareTableView() {
tableView = UITableView(frame: self.view.bounds, style: .Plain)
tableView.dataSource = self
tableView.delegate = self
tableView.separatorColor = UIColor.clearColor()
self.view.addSubview(tableView)
}
Check Following :
1 numberOfRowsInSection see the Number of return value
2 if step1 is called and return right value still not calling cellForRowAtIndexPath that means your table view height is less then height of cell or table view height is 0 , print tableview bounds and also check superview of tableview has clips to bounds to TRUE
tableview datasource method cellForRowAtIndexPath is depending on numberOfRowsInSection . So it may be possiblity that you having zero item in newsItems

Resources