When to use dequeueReusableCellWithIdentifier vs dequeueReusableCellWithIdentifier : forIndexPath - ios

There are two overloads for dequeueReusableCellWithIdentifier and I'm trying to determine when should I use one vs the other?
The apple docs regarding the forIndexPath function states, "This method uses the index path to perform additional configuration based on the cell’s position in the table view."
I'm not sure how to interpret that though?

The most important difference is that the forIndexPath: version asserts (crashes) if you didn't register a class or nib for the identifier. The older (non-forIndexPath:) version returns nil in that case.
You register a class for an identifier by sending registerClass:forCellReuseIdentifier: to the table view. You register a nib for an identifier by sending registerNib:forCellReuseIdentifier: to the table view.
If you create your table view and your cell prototypes in a storyboard, the storyboard loader takes care of registering the cell prototypes that you defined in the storyboard.
Session 200 - What's New in Cocoa Touch from WWDC 2012 discusses the (then-new) forIndexPath: version starting around 8m30s. It says that “you will always get an initialized cell” (without mentioning that it will crash if you didn't register a class or nib).
The video also says that “it will be the right size for that index path”. Presumably this means that it will set the cell's size before returning it, by looking at the table view's own width and calling your delegate's tableView:heightForRowAtIndexPath: method (if defined). This is why it needs the index path.

dequeueReusableCellWithIdentifier:forIndexPath: will always return a cell. It either re uses existing cells or creates a new one and returns if there are no cells.
While, the traditional dequeueReusableCellWithIdentifier: will return a cell if it exists i.e if there is a cell which can be reused it returns that else it returns nil. So you would have to write a condition to check for nil value as well.
To answer your question use dequeueReusableCellWithIdentifier: when you want to support iOS 5 and lower versions since dequeueReusableCellWithIdentifier:forIndexPath is only available on iOS 6+
Reference : https://developer.apple.com/library/ios/documentation/uikit/reference/UITableView_Class/Reference/Reference.html#//apple_ref/occ/instm/UITableView/dequeueReusableCellWithIdentifier:forIndexPath:

I have never understood why Apple created the newer method, dequeueReusableCellWithIdentifier:forIndexPath:. Their documentation on them is not complete, and is somewhat misleading. The only difference I've been able to discern between the two methods, is that that older method can return nil, if it doesn't find a cell with the identifier passed in, while the newer method crashes, if it can't return a cell. Both methods are guaranteed to return a cell, if you have set the identifier correctly, and make the cell in a storyboard. Both methods are also guaranteed to return a cell if you register a class or xib, and make your cell in code or a xib file.

For short:
dequeueReusableCell(withIdentifier, for) only works with prototype
cells. If you tried to use it when the prototype cell is absence, it would crash the app.
Hollemans M. 2016, Chapter 2 Checklist, IOS Apprentice (5th Edition). pp: 156.

The main difference is you can not register two cells for the same indexPath while only using the reuse identifier you can do it, and both can return nil if the cells are not registered against that table view

I would recommend to use both if you are using dynamic generated content. Otherwise your app might crash unexpectedly. You could implement your own function to retrieve an optional reusable cell. If it is nil you should return an empty cell that is not visible:
Swift 3
// Extensions to UITableView
extension UITableView
{
// returns nil, if identifier does not exist.
// Otherwise it returns a configured cell for the given index path
open func tryDequeueReusableCell (
withIdentifier identifier: String,
for indexPath: IndexPath) -> UITableViewCell?
{
let cell = self.dequeueReusableCell(withIdentifier: identifier)
if cell != nil {
return self.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
}
return nil
}
}
And the extension to return an empty cell:
// Extension to UITableViewCell
extension UITableViewCell
{
// Generates an empty table cell that is not visible
class func empty() -> UITableViewCell
{
let emptyCell = UITableViewCell(frame:CGRect(x:0, y:0, width:0, height:0))
emptyCell.backgroundColor = UIColor.clear
return emptyCell
}
}
A complete example of how to use it:
import Foundation
import UIKit
// A protocol is used to identify if we can configure
// a cell with CellData
protocol ConfigureAbleWithCellData
{
func configure(_ data: CellData)
}
class MyCustomTableViewCell :
UITableViewCell,
ConfigureAbleWithCellData
{
#IBOutlet weak var title:UILabel! = nil
func configure(_ data: CellData)
{
self.title.text = data.title
}
}
// This actually holds the data for one cell
struct CellData
{
var title:String = ""
var reusableId:String = ""
}
class CosmoConverterUnitTableViewController:
UIViewController,
UITableViewDelegate,
UITableViewDataSource
{
// Storage
var data = Array<Array<CellData>>()
func loadData()
{
var section1:[CellData] = []
var section2:[CellData] = []
section1.append(CellData(title:"Foo", reusableId:"cellType1"))
section2.append(CellData(title:"Bar", reusableId:"cellType2"))
data.append(section1)
data.append(section2)
}
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int
{
return data[section].count
}
public func numberOfSections(in tableView: UITableView) -> Int
{
return data.count
}
func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard
indexPath.row < data[indexPath.section].count
else
{
fatalError("this can't be")
}
let cellData = data[indexPath.section][indexPath.row]
if let cell = tableView.tryDequeueReusableCell(
withIdentifier: cellData.reusableId,
for: indexPath)
{
if let configurableCell = cell as? ConfigureAbleWithCellData
{
configurableCell.configure(cellData)
}
else
{
// cell is not of type ConfigureAbleWithCellData
// so we cant configure it.
}
return cell
}
// id does not exist
return UITableViewCell.empty()
}
}

Related

Content of a cell in static tableview isn't shown SWIFT 3

Here is my implementation of tableView(_:cellForRowAt:):
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let index = indexPath.section
let weekDay = WeekDays.day(at: index)
if self.availability.numberOfTimeslots(for: weekDay) == 0 {
let cell = NotSelectedCell(style: .default, reuseIdentifier: nil)
return cell
}
return UITableViewCell()
}
Here is my code for my custom table view cell:
class NotSelectedCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.backgroundColor = .red
self.textLabel?.numberOfLines = 0
self.textLabel?.textAlignment = .center;
self.textLabel?.text = "Not Available"
}
}
I've also tried initializing custom cell cell = NotSelectedCell() the result is the same. The content isn't shown. dataSource or viewDelegate aren't the problem as I'm working with UITableViewController.
Here's an image
The problem is awakeFromNIB "prepares the receiver for service after it has been loaded from an Interface Builder archive, or nib file." But you're instantiating this programmatically, so that method isn't called. You could theoretically move the code to init(style:reuseIdentifier:), make sure to call super in your implementation, and do any additional customization after that point.
But, you generally wouldn't programmatically instantiate cells when using static cells. (It's the point of static cells, that IB takes care of everything for you.) You generally don't implement UITableViewDataSource at all when using static cells.
I would advise using dynamic table and have two cell prototypes, one with reuse identifier of "NotAvailable" and one with "Available" (or whatever identifiers you want). Then programmatically instantiate the cell with the appropriate identifier. (By the way, this also has the virtue that your cell with "NotAvailable" can be designed entirely in IB, and no code is needed, for that cell at least.) This way, the storyboard takes care of instantiating the appropriate cell.
So, here I have two cell prototypes in my dynamic table, one for "not available" and one for "available":
Then the code would look at the model to figure out which to instantiate:
// for the complicated cell where I want to show details of some window of availability, add IBOutlets for that cell's labels
class AvailableCell: UITableViewCell {
#IBOutlet weak var startLabel: UILabel!
#IBOutlet weak var stopLabel: UILabel!
#IBOutlet weak var doctorLabel: UILabel!
}
// some super simple model to represent some window of availability with a particular doctor in that office
struct Availability {
let start: String
let stop: String
let doctor: String
}
class ViewController: UITableViewController {
let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
let available = ...
override func numberOfSections(in tableView: UITableView) -> Int {
return days.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return available[days[section]]?.count ?? 1
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return days[section]
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// see if there are any available windows for the given day, if not, return "not available" cell
guard let availabilities = available[days[indexPath.section]] else {
return tableView.dequeueReusableCell(withIdentifier: "NotAvailable", for: indexPath)
}
// otherwise, proceed with the more complicated "Available" cell where I have to populate various labels and the like
let cell = tableView.dequeueReusableCell(withIdentifier: "Available", for: indexPath) as! AvailableCell
let availability = availabilities[indexPath.row]
cell.startLabel.text = availability.start
cell.stopLabel.text = availability.stop
cell.doctorLabel.text = availability.doctor
return cell
}
}
And that would yield:
Now, clearly, I just whipped up a super primitive model, and didn't do any UI design in the "available" cell prototype other than inserting three labels. But it illustrates the idea: If your dynamic table has multiple unique cell designs, just implement cell prototypes for each with unique identifiers and instantiate the appropriate one. And this way, you enjoy full cell reuse, minimize how much visual design you have to do programmatically, etc.
You are not supposed to use the cellForRow:atIndexPath method when using static cells. The cells are static, so the loading flow is different. What i'd suggest is to connect the cells individually from the interface builder to your view controller.
STILL, if you want to do it this way you have to get your cells by calling "super" since that's the class who is actually generating your static cells.
UITableView with static cells without cellForRowAtIndexPath. How to set clear background?
EDIT:
I just noticed that this is wrong:
if self.availability.numberOfTimeslots(for: weekDay) == 0 {
let cell = NotSelectedCell(style: .default, reuseIdentifier: nil)
return cell
}
You have to use the "dequeueReusable" method or something. Then again, these are STATIC Cells, so you should just be linking the cells directly from the interface builder.

What does Apple mean by term "registers" in regards to creating new tableView cells?

I'm working through an exercise which uses tableviews. I noticed within a test during the exercise, they use a method I haven't needed in the past when implementing tableviews from storyboards. The method is:
func register(AnyClass?, forCellReuseIdentifier: String)
After reading the short description of this function in the reference pages. I'm curious to know what does apple mean by term "registers"? I half assume that since we are doing this exercise programmatically at the moment, this function is only needed if you're creating UITableviews programmatically. If this statement is incorrect, please let me know as I'd like to learn more.
Here is the code from the example:
func test_CellForRow_DequesCellFromTableView(){
let mockTableView = MockTableView()
mockTableView.dataSource = sut
mockTableView.delegate = sut
mockTableView.register(ItemCell.self, forCellReuseIdentifier: "ItemCell")
sut?.itemManger?.add(ToDoItem.init(title: "Foo"))
mockTableView.reloadData()
_ = mockTableView.cellForRow(at: IndexPath.init(row: 0, section: 0))
XCTAssertTrue(mockTableView.cellGotDequeed)
}
The DequeueReusable methods are there to check if any reusable cells are left before creating new ones. Hope you have an idea about the working of reusable cells
What happens when the queue is empty? Now we do need to create a cell. We can follow 2 methods to create a cell,
Create cell manually
Create it automatically by registering cell with a valid xib file
METHOD 1
if you do it with manually, you must check cell is empty or not after dequeueReusableCell check. Just like below,
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Reuse an old cell if exist else return nil
let cell:UITableViewCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as UITableViewCell!
//check cell is nil if nil you want to allocate it with proper cell
if(cell == nil){
//create cell manually
cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "CellSubtitle")
}
// do stuff to the cell here
return cell
}
METHOD 2
We could create the cell manually like above which is totally fine. But it would be convenient if the table view would create the cell for us directly.
That way we don't have to load it from a nib or instantiate it.
For registering a cell with a xib or class we use func register(AnyClass?, forCellReuseIdentifier: String) method. Let see an example,
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(MyCell.self, forCellReuseIdentifier: "Cell")
}
// ...
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath:indexPath) as MyCell
// no "if" - the cell is guaranteed to exist
// ... do stuff to the cell here ...
cell.textLabel.text = // ... whatever
// ...
return cell
}
You are "registering" your custom Cell class - ItemCell - for reuse as a cell for your tableview.
See: https://developer.apple.com/reference/uikit/uitableview/1614888-register
"Register" tells XCode that the cell exists. A cell is registered under a "reuse identifier." This is a unique string that corresponds to your TableViewCell, in this case ItemCell.
A cell can also be registered in the Storyboard by filling out the "Identifier" in the cell's attributes inspector.

UITableview with 2 cells

I am trying to build an app with 2 cell on a tableview
the header that will have the Headlines and the second that will have the normal feeds.
My question is how to connect this two cells
The code for the raw feeds are working
Example:
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell
// Configure the cell...
let post = self.posts[indexPath.row]
cell.title.text = post.title?.utf8Data?.attributedString?.string
if let imageUrl = post.imageUrl {
cell.imgView?.downloadImage(from: imageUrl)
}
let myFormatter = DateFormatter()
myFormatter.dateStyle = .none
myFormatter.timeStyle = .short
cell.pubDate.text = myFormatter.string(from: post.pubDate!) // What gives?
return cell
}
}
You can do this by filling a solid color, like white, for both the cells and set Table View separator to none.
tableView.separatorStyle = .none
And if you want to create a serious of such combined cells, then add a view to the bottom of the second cell, i.e. Feed Row, and fill that view with other color, like grey. This view will work as a separator for serious.
I suggest you let your tableview empty in storyboard. (delete all cells)
What I usually do, and what I feel is more flexible and cleaner in terms of code and files, is always use custom cells.
Create a subclass of UITableViewCell for each cell type you need (here, 2). One could be HeaderCell, and the other could be FeedCell.
If you use the "Create UITableViewCell class" I think you can find in Xcode (haven't used it in a long time), it should create the class for you, as well as a .xib file. If you don't have the corresponding .xib just create it and link it manually.
The .xib file is where you will put your labels, connect the outlets and maange everything you need. That's where you'll remake the cells you deleted ealier on. I think you can actually cut and paste in the xib and it might work.
Now you have a custom class, with its corresponding xib, which you can use as a cell.
Make sure your tableview is connected as delegate and datasource in storyboard (it should already be done in your setup).
In ViewDidLoad you will need to register all your cell classes to the tableview.
That is done using this method call on your tableview property (outlet)
I'm not sure how to write it in swift, but it's
MyTableView.Register(nib, key) in pseudo code.
Both the parameters come from your custom cell class (it's the .Nib and the class name, which you could hardcode). I usually do MyHeaderCell.Nib and MyHeaderCell.Key that are put in static, but that you can do however you want.
Now that your tableview is aware of the cells it will have to display, you just have to manage it in your cellForRow method.
Simply do it by index :
var cell;
if (indexPath.Row == 0)
{
cell = tableView.dequeueReusableCell(withIdentifier: "HeaderCell", for: indexPath) as! HeaderCell
}
else
{
cell = tableView.dequeueReusableCell(withIdentifier: "FeedCell", for: indexPath) as! FeedCell
}
let post = self.posts[indexPath.row]
cell.title.text = post.title
return cell
And there you go :)
I've never done swift so there are certainly syntax mistakes but the idea is how you should do it.

How to set datasource and delegate Outside of View Controller

This might sound like an odd question but I'm trying to implement the BEMSimpleLineGraph library to generate some graphs that I have place in a UITableView. My question is how I reference an external dataSource and Delegate to have different graphs placed in each cell (BEMSimpleLineGraph is modelled after UITableView and UICollectionView). I currently have something like this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: FlightsDetailCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as FlightsDetailCell
cell.userInteractionEnabled = false
if indexPath.section == 0 {
cell.graphView.delegate = GroundspeedData()
cell.graphView.dataSource = GroundspeedData()
return cell
}
if indexPath.section == 1 {
cell.graphView.delegate = self
cell.graphView.dataSource = self
return cell
}
return cell
}
My dataSource and Delegate for section 1 is setup properly below this and the GroundspeedData class looks like this:
class GroundspeedData: UIViewController, BEMSimpleLineGraphDelegate, BEMSimpleLineGraphDataSource {
func lineGraph(graph: BEMSimpleLineGraphView!, valueForPointAtIndex index: Int) -> CGFloat {
let data = [1.0,2.0,3.0,2.0,0.0]
return CGFloat(data[index])
}
func numberOfPointsInLineGraph(graph: BEMSimpleLineGraphView!) -> Int {
return 5
}
}
For some reason when I run the app, Xcode reports that it cannot find the dataSource for section 0, specifically "Data source contains no data.". How should I otherwise reference this alternate dataSource?
cell.graphView.delegate = GroundspeedData()
cell.graphView.dataSource = GroundspeedData()
One problem is: the delegate and data source are weak references. That means they do not retain what they are set to. Thus, each of those lines creates a GroundspeedData object which instantly vanishes in a puff of smoke. What you need to do is make a GroundspeedData object and retain it, and then point the graph view's delegate and data source to it.
Another problem is: do you intend to create a new GroundspeedData object or use one that exists already elsewhere in your view controller hierarchy? Because GroundspeedData() creates a new one - with no view and no data. You probably mean to use a reference to the existing one.

Adding various content type within Table View Cell

I am new to IOS programming and will need some direction here.
I am trying to create a tableview with each rows having a image and some text.
I am able to take a TableViewController and programmatically was able to add basic text and rows. But can you please tell me how should i add more complex content. Trying to achieve something like this using program.
My current code looks like this in my TableViewController and its able to print a text message on each row.
override func numberOfSectionsInTableView(tableView: UITableView!) -> Int {
return 1
}
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell? {
var mycell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("frontcell", forIndexPath: indexPath) as UITableViewCell
mycell.textLabel.text = "Just a generic message"
return mycell
}
There is a
mycell.contentView
which takes in UIView. But how does it works? How should this object be created?
Appreciate your help.
------Update -----
Thanks to Suryakant for helping out. Answer below for his step by step how to do. Any one who needs the source code can use this. http://pastebin.com/ZfNqK4tW
Though you can achieve it by default UITableViewCell also, as #meda mention in his answer,
but it seems, you want different UIImageView size and 2 UILabels with different font size or may be some more controls there. For Achieving that you need to customize UITableViewCell and you can do that by subclassing UITableViewCell class.
Create a class by subclassing UITableViewCell.
e.g. your subclass say MyCell look like —
2.Go to storyboard and select prototypeCell and select Identity inspector, in Class type your custom class name (e.g MyCell )in place of UITableViewCell.
drag-n-drop all the controls you need and link with their IBOutlets (From MyCell to prototypeCell).
This goes as below..
3.Now goto Attributes Selector and give some Identifier to your MyCell, you can give any string you want.
4.Goto the class where you implemented UITableView delegates and update your cellForIndexPath as bellow
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath:
NSIndexPath!) -> UITableViewCell! {
let kCellIdentifier:String = "cell"
var cell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier) as MyCell!
if cell == nil {
// register Custom UITableView Class to UITableView
tableView.registerClass(MyCell.classForCoder(), forCellReuseIdentifier: kCellIdentifier)
cell = MyCell(style: UITableViewCellStyle.Default, reuseIdentifier: kCellIdentifier)
}
if var label = cell.cellMyCity{
label.text = cityList[indexPath.row]
}
if var label = cell.cellMyCountry{
label.text = countryList[indexPath.row]
}
if var imageView = cell.imageView{
imageView.image = UIImage(named :"img.png")
}
return cell
}
For reference you can see example code here.
You would create a subclass of UITableViewCell and then assign values to the property of your cell
mycell.textLabel.text = "Just a generic message"
mycell.detailTextLabel.text = "Just a detail message"
mycell.imageView.text = myImage
And for that you would use only one prototype Cell no need to duplicate them.

Resources