Why does UITableView cache cells when reloading data? - ios

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
}

Related

I can't dequeueReusableCell when the Cell is in its own class

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...

Swift table view not showing contents

I wanted to give a go at swift, and looked at several tutorials. I tried to implement a TableView.
Here is my code :
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var items: [String] = ["lol1", "lol2", "lol3"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1;
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "cell")!
cell.textLabel?.text = self.items[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
print("You selected cell #\(items[indexPath.row])!")
}
}
MY IBOutlet is connect to the tableview I inserted in the storyboard.
When I run it, I have a TableView, but it's missing contents.
From what I gathered through some (more or less outdated) tutorials, I shouldn't have anything more to do, what am I missing ?
Where are you set dataSource and Deleagte methods of TableView?
use this code
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.tableView.dataSource = self
self.tableView.delegate = self
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
2 possible reasons:
If the cell is designed as prototype cell you must not register the cell.
dataSource and delegate of the table view must be connected to the controller in Interface Builder or set in code.

Dynamic Table View within Static Table View 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 {
}
}

How to show UITableView Cell in UITableView without array?

I have this code:
class UserProfilViewController: UIViewController {
// Outlets
#IBOutlet weak var userProfileTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.navigationItem.title = "Profil"
}
}
// MARK: - Table View Data Source
extension UserProfilViewController {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserProfilCell", for: indexPath)
return cell
}
}
My project in bitbucket: https://bitbucket.org/trifek/karta-nauka/src/master/
I placed one tableviewcell cell on the tableview (UserProfil.storyboard). I have a form on it that I would like to display in this cell. The problem is the cell does not display. Does anyone know how to fix it?
As per the code you have shared, Please change your code to following.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserProfilCell", for: indexPath) as! UserProfilTableViewCell
return cell
}
Let me know in case of any queries.
IMHO, first try to clear your requirements. If you want to display fix number of cells then you can simply use static cells. If your cells are dynamic i.e their number depends on some calculation or other logic, then you can use dynamic cell. While using dynamic cell, verify if you have registered it or not (if you are using xib for cell) and also verify for the cell identifier.
#Lukasz
Please use the below code for this.
class UserProfileViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setUIComponents()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
private func setUIComponents(){
registerNibs()
}
}
extension UserProfileViewController: UITableViewDelegate, UITableViewDataSource{
internal func registerNibs(){
tableView.register(UINib(nibName: String(describing: UserProfileTableCell.self), bundle: Bundle.main), forCellReuseIdentifier: kUserProfileCellReuseId)
}
//MARK: TableView Methods -
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sessionCell: UserProfileTableCell.self = tableView.dequeueReusableCell(withIdentifier: kUserProfileCellReuseId, for: indexPath) as! UserProfileTableCell.self
cell.titleLabel.text = “TEST”
return sessionCell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
class UserProfileTableCell: UITableViewCell {
//Set the "kUserProfileCellReuseId" in nib to register identifier.
let kUserProfileCellReuseId = "kUserProfileCellReuseId"
//MARK: - Override Methods
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
setUIComponents()
}
private func setUIComponents(){
}
}
You never declare that your view controller conforms to the UITableViewDataSource or UITableViewDelegate protocols. Given that you don't do that, you would not be able to set your view controller as the data source or delegate of your table view.

Swift / How to populate UITableViewCells

I have a controller with a Table View on it. Have defined my cells in a view and then I bind it to the table view. (Please excuse the terminology). App builds and runs fine but none of the labels defined in the cell show anything. Entire table is blank except for the cell separators. I've followed numerous tutorials and searched SO extensively for reasons, how to create (Create a Table View - Apple docs), settings to check, all with the same results. I've also tried cleaning, restarting Xcode, etc. This is Xcode 8/Swift 3.
The view controller has a table view and I register the cell class like so in the viewDidLoad
self.tableView.registerCellClass(MenuTableViewCell.self)
UITableViewDataSource code
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return menusItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MenuTableViewCell.identifier, for: indexPath) as! MenuTableViewCell
let item = menusItems[indexPath.row]
cell.setData(item)
return cell
}
menuItems is an array of a struct I have that is created before anything else is done
BaseTableViewCell
open class BaseTableViewCell: UITableViewCell {
class var identifier: String { return String.className(self) }
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setup()
}
open override func awakeFromNib() {
}
open func setup() {
}
open override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
open class func height() -> CGFloat {
return 48
}
open func setData(_ data: Any?) {
if let menuText = data as? String {
self.textLabel?.text = menuText
}
if let menuImage = data as? UIImage {
self.imageView?.image = menuImage
}
}
}
Table View Cell code - inherits the BaseTableViewCell
class MenuTableViewCell: BaseTableViewCell {
//MARK: Properties
#IBOutlet weak var name: UILabel!
#IBOutlet weak var icon: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override func setData(_ data: Any?) {
if let data = data as? MenuTableViewCellData {
NSLog("menu name - %#", data.text)
self.icon?.image = data.image
self.name?.text = data.text
}
}
}
All menu names are written to the debug area so the code is being executed.
I don't know if there is hook that is missing or if something else. Please let me know if anything further is also needed. Any help is appreciated.
Screenshots showing IBOutlets connected
Here is the delegate and data source from the controller
extension LeftViewController : UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return MenuTableViewCell.height()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let menu = LeftMenu(rawValue: indexPath.row) {
self.changeViewController(menu)
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if self.tableView == scrollView {
}
}
}
extension LeftViewController : UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return menusItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MenuTableViewCell.identifier, for: indexPath) as! MenuTableViewCell
let item = menusItems[indexPath.row]
cell.setData(item)
return cell
}
}
Thanks everyone for your help. I was able to get it working. I removed the XIB file, created the layout in the view and then wired it up from there (followed here for how to).
Verify that you've connected the IBOutlets for name and icon in MenuTableViewCell. Assuming what you say is true, that the NSLog in setData is called, chances are you haven't connected the IBOutlets for the table view cell subclass.
You may have 0-height rows. I see a height method in there - what is calling it? Do you have an implementation of
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
somewhere?
Are you setting the table view's rowHeight?
Can you check whether name and icon have proper constraints

Resources