I'm trying to move my "cell" code from the "func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)" function to its own class. I have registered the forCellReuseIdentifier and class with the ViewController (I think). And I have set the reuseIdentifier for the prototype cell to "MealSelector". However, when I run the code, all of the cells are blank. Why is the data not populating the tables?
Here is the view controller:
import UIKit
class MealSelectorController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var MealTable: UITableView!
#IBOutlet weak var ConfirmOrderButton: UIButton!
var recipes: [[String?]] = []
override func viewDidLoad() {
super.viewDidLoad()
for _ in 0 ..< 3 {
let recipe: Recipe = APICaller.getNewRecipe()
self.recipes.append([recipe.title, recipe.description])
}
MealTable.dataSource = self
MealTable.delegate = self
MealTable.register(MealSelectorCell.self, forCellReuseIdentifier: "MealSelector")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.recipes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MealSelector")
return cell!
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 241
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
Here is the cell:
import UIKit
class MealSelectorCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In a comment, you say:
It is not using my prototype cell at all to build the TableView
If that means a prototype cell in a storyboard, then delete this line:
MealTable.register(MealSelectorCell.self, forCellReuseIdentifier: "MealSelector")
That line means: Do not use the storyboard prototype. So you will need to delete it.
You will also need to set the class of the prototype cell in the storyboard.
However, I would then expect your app to crash because your init(coder:) implementation says to crash.
fatalError("init(coder:) has not been implemented")
You’ll probably want to fix that too...
Related
I'm creating a table view ui programmatically and the code i followed has no problems but mine kept telling me i have the error above? Please help
The error shows that it is at this line: override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
This is a fragment of my code:-
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath as IndexPath)
}
class MyCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
}
}
Im not sure but seem you have tableView in UIViewController? If like this, we don't need override before func cellForRowAtIndexPath . Just remove override text.
cellForRowAtIndexPath is Swift 2 code. Please look for tutorials providing contemporary code.
The syntax of Swift 3+ is
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
The override keyword is only required if the class is a subclass of UITableViewController
If you use UIViewController, just add UITableViewDataSource and UITableViewDelegate methods as like below example and don't include override keyword:
class MyViewController: UIViewController , UITableViewDataSource, UITableViewDelegate{
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
}
//Other UITableViewDataSource and UITableViewDelegate methods goes here
}
If you use UITableViewController, you can override the UITableViewDataSource and UITableViewDelegate methods as like below example:
class MyTableViewController: UITableViewController{
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
}
//override other UITableViewDataSource, UITableViewDelegate methods here
}
Hope it helps!
If you were to call tableView.reloadData() on a table with custom UITableViewCell, then it will cache those cells when you reload data. (It is discussed here: https://github.com/nicklockwood/FXForms/issues/92)
If you continue reloading data, after a while, there could be 30/40 cells that are cached and retained. My question is: why is that?
In particular:
1) What possible usage could there be to retain cells that have since been lost after reloading the table?
2) What could be done to prevent/reduce the memory cache storing the cells?
3) Most importantly, why does this happen? Why would cells be retained when reloadData() is called?
Example
To give you a basic example in code, click on one of the cells, then, when you see the objects in memory, there are more retained cells:
ViewController.Swift
import UIKit
class ViewController: UIViewController {
let tableViewController = QuizTableViewController(style: .grouped)
override func viewDidLoad() {
super.viewDidLoad()
self.setupTableViewController()
}
private func setupTableViewController() {
self.view.addSubview(tableViewController.view)
let topAnchor = self.tableViewController.view.topAnchor.constraint(equalTo: self.view.topAnchor)
let leadingAnchor = self.tableViewController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor)
let trailingAnchor = self.tableViewController.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor)
let bottomAnchor = self.tableViewController.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
NSLayoutConstraint.activate([topAnchor, leadingAnchor, trailingAnchor, bottomAnchor])
}
}
QuizTableViewController.Swift
import UIKit
class QuizTableViewController: UITableViewController {
override init(style: UITableViewStyle) {
super.init(style: style)
self.view.translatesAutoresizingMaskIntoConstraints = false
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = QuizCell(subText: "HELLO WORLD")
cell.textLabel?.text = "THIS IS WHAT IS DISPLAYED"
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.reloadData()
}
}
QuizCell.Swift
import UIKit
class QuizCell: UITableViewCell {
weak var subText: UILabel?
init(subText: String) {
self.subText = UILabel()
self.subText?.text = subText
super.init(style: .default, reuseIdentifier: "QuizCell")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("This is called after the cache is full.")
}
}
The problem is that you are not using the proper approach to creating table cells. You are explicitly creating a new instance of QuizCell in the cellForRowAt. What you should be doing is dequeueing a reusable cell using dequeueReusableCell. Then your issue goes away because the table will only keep the minimum number of cells needed.
Please find any one of the countless tutorials on using table views for proper examples.
Like #rmaddy mentioned, the problem is how you are dequeuing the cell in cellForRowAt function. Instead, change it to something similar to:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "QuizCell", for: indexPath) as! QuizCell
cell.textLabel?.text = "THIS IS WHAT IS DISPLAYED"
return cell
}
I'm fairly new into Swift programming and right now I'm implementing a dynamic table view within a static table view's cell. I know there are plenty of solutions on stackoverflow already but I realised that most of them are in Obj-C which I'm not very familiar with it yet.
Basically, I have a TableView that is set as dynamic in one of the cell of a static table view which is part of the main table view controller. The problem I am having now is there doesn't seem to be a way to implement the data source functions without declaring them for the static table view. I have declared an #IBOutlet for the dynamic table (let's call it dynamicTableView in this scenario).
I have managed to get the override func numberOfSections(in tableView: UITableView) working by returning 1 if the tableView is not dynamicTableView as in the following code:
override func numberOfSections(in tableView: UITableView) -> Int {
if tableView == dynamicTableView {
return data.count
}
else {
return 1
}
}
However, the problem I am having now is implementing the override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath). I have no idea what to be returned if the tableView parameter is not dynamicTableView, but for the static table view.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == dynamicTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "dynamic", for: indexPath) as! dynamicTableViewCell
cell.update(data[indexPath.row]) // A helper function declared in the dynamicTableViewCell.swift
return cell
}
else {
// What to return here?
}
}
Thanks!
Edit: What I meant was I can't seem to have a cellForRowAt data source function that does not affect my static table view.
If there is a value in numberForRows then you have to retutn a cell like this
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == bloggerReviewTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "dynamic", for: indexPath) as! dynamicTableViewCell
cell.update(data[indexPath.row]) // A helper function declared in the dynamicTableViewCell.swift
return cell
}
else {
// What to return here?
let cell = tableView.dequeueReusableCell(withIdentifier: "other", for: indexPath) as! OtherTableCell
return cell
}
}
//
but if the return is zero then there is no need for the if statement inside cellForRowAt as it won't be called for the other table
If the static tableview cells are fairly distinct, they can be individually subclassed.
The dynamic tableview/collectionview can be added in required subclass of static tableview cell.
//class for static tableview
let reviewCellId = "reviewCell"
class StaticTableClass: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
//register static cell classes
tableView.register(ReviewCell.self, forCellReuseIdentifier: reviewCellId)
//..
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reviewCellId, for: indexPath)
return cell
}
}
Create a separate ReviewCell class which will contain the dynamic UITableView like so.
This way one class will handle methods of only one tableview.
class ReviewCell: UITableViewCell, UITableViewDelegate, UITableViewDataSource {
lazy var dynamicTableView: UITableView = {
let tv = UITableView()
tv.delegate = self
tv.dataSource = self
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
addSubview(dynamicTableView)
dynamicTableView.register(UITableViewCell.self, forCellReuseIdentifier: "dynamicCellId")
}
// add further tableview methods in here
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
}
}
After some refactoring I decided to create a custom tableview that looks a bit like:
class BaseTable: UITableView, UITableViewDelegate, UITableViewDataSource {
var rowsInSection: Int { return 0}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.delegate = self
self.dataSource = self
self.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rowsInSection
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.deselectRow(at: indexPath, animated: true)
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "headerCell")!
return cell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 40
}
And then I subclass it like this:
class CustomTable: BaseTable{
override var rowsInSection: Int {return payArray.count }
}
This works fine, however I've noticed none of the subclassed didSelectRowAt are being called??? Can anyone help?
Well, you should follow this things:
You should split up logic from initWithCoder: to external method such as and call it in initWithFrame:, because different approaches called different init methods. Such as
func setUpTableView() {
self.delegate = self
self.dataSource = self
self.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
For clarity in your code, you should explicitly call super method from child class methods
Otherwise, I strongly recommend you not to use this approach, better use standard UITableView in controller, and different classes, that implements protocols alongside with custom UITableViewCell's
This is bad design. The reason there are delegate and data source protocols for UITableViews is because view objects should be reusable. You're making your BaseTable class to function as a controller as well as a view.
To answer your question, are you sure it's not being called? You only have deselectRow(at:animated:) calling from tableView(didSelectRowAt:)
I created a custom class for a cell in my program. When I try to use it in another class to create a cell I keep getting the error Cannot invoke 'init' with an argument list of type '(style: UITableViewCellStyle, reuseIdentifier: StringLiteralConvertible)'. Can anybody point me in the right direction here? I would really appreciate any help. I tried changing the class to inherit form UITableViewController so I can use this var cell: bookCell = self.tableView.dequeueReusableCellWithIdentifier("cell1") as bookCell but it crashes the program if I try to make the class inherit from tableviewcontroller.
import UIKit
class bookCell: UITableViewCell {
#IBOutlet var bookImage: UIImageView!
#IBOutlet var bookDescription: UILabel!
#IBOutlet var bookPosterUsername: UILabel!
}
import UIKit
class SubjectBooksViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var navigationBarTitle: UINavigationBar!
override func viewDidLoad() {
super.viewDidLoad()
self.navigationBarTitle.topItem?.title = "\(selectedCourse)"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 100
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell : bookCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "subjectCell")
//var cell: bookCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "subjectCell")
//var cell: bookCell = self.tableView.dequeueReusableCellWithIdentifier("cell1") as bookCell
return cell
}
}
Update code:
import UIKit
class SubjectBooksViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var navigationBarTitle: UINavigationBar!
#IBOutlet var myTableView: UITableView! //Outlet for your table View
override func viewDidLoad() {
super.viewDidLoad()
self.myTableView.dataSource = self //If you have not done in IB
self.myTableView.registerNib(yourCellNib, forCellReuseIdentifier: "subjectCell")
self.navigationBarTitle.topItem?.title = "\(selectedCourse)"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 100
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell : bookCell = tableView.dequeueReusableCellWithIdentifier("subjectCell", forIndexPath: indexPath)
return cell
}
}
In viewDidLoad() in line :
self.myTableView.registerNib(yourCellNib, forCellReuseIdentifier: "subjectCell")
replace yourCellNib with loaded nib file for your custom cell.
Registering your nib file is required if you plan to reuse cell in your table view. It is always a good idea to reuse cells.
You need to override this function and get your custom cell in this manner:
override func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cell = tableView.dequeueReusableCellWithIdentifier(
"subjectCell",
forIndexPath: indexPath) as bookCell
Your class name really should be BookCell though, with an uppercase "B". Just to keep with existing standards