A Swift mixin to use UITableViewCells, UICollectionViewCells and UIViewControllers in a type-safe way, without the need to manipulate their String-typed reuseIdentifiers. This library also supports arbitrary UIView to be loaded via a XIB using a simple call to loadFromNib()
- Mark your
UITableViewCellclasses to conform to eitherReusableorNibReusable(no additional code to implement!) - Then simply use
tableView.dequeueReusableCell(indexPath: indexPath) as MyCustomCellto get a dequeued instance of the expected cell class. No need for you to manipulatereuseIdentifiersmanually! - Use the same for
UICollectionViewCells
class MyCustomCell: UITableViewCell, NibReusable { }
…
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: MyCustomCell = tableView.dequeueReusableCell(indexPath: indexPath)
… // configure the cell, which is already of the expected MyCustomCell type
return cell
}No more force-casting the returned UITableViewCell instance down to your MyCustomCell class, and no more fear that you'll mismatch the reuseIdentifier and the class you down-cast to. Now all you have is a beautiful code and type-safe cells!
For more information on how this works, see my dedicated blog post about this technique.
- Mark your
UIViewcustom classes to conform toNibLoadable(no additional code to implement!) - Then simply use
MyCustomView.loadFromNib()to create an instance of that XIB-based view
- Mark your
UIViewControllercustom classes to conform toStoryboardBased(if they are the initial ViewController) orStoryboardSceneBased(if they're not) - Then imply use
YourCustomViewController.instantiate()to create an instance of that Storyboard-based ViewController.
First, declare your cells to conform to:
- the
Reusableprotocol if they don't depend on a NIB (this will useregisterClass(…)to register the cell) - the
NibReusableprotocol if they use aXIBfile for their content (this will useregisterNib(…)to register the cell)
So for example to create a UITableViewCell subclass which doesn't use a XIB (either because it is only created via code, or because it will be registered automatically via a storyboard):
class CodeBasedCustomCell: UITableViewCell, Reusable {
// By default this cell will have a reuseIdentifier of "CodeBasedCustomCell"
// unless you provide an alternative implementation of `var reuseIdentifier`
// No need to add anything to conform to Reusable. you can just keep your normal cell code
@IBOutlet private weak var label: UILabel!
func fillWithText(text: String?) { label.text = text }
}And to create a UITableViewCell subclass whose content is based on a XIB:
class NibBasedCustomCell: UITableViewCell, NibReusable {
// Here we provide a nib for this cell class (which, if we don't override the protocol's
// default implementation of `nib`, will use a XIB of the same name as the class)
// No need to add anything to conform to Reusable. you can just keep your normal cell code
@IBOutlet private weak var pictureView: UIImageView!
func fillWithImage(image: UIImage?) { pictureView.image = image }
}If you create a XIB-based cell, don't forget to set its Reuse Identifier field in Interface Builder to the same string as the name of the cell class itself.
This works exactly the same as UITableViewCell.
So for a Code-based UICollectionViewCell subclass:
// A UICollectionViewCell which doesn't need a XIB to register
// Either because it's all-code, or because it's registered via Storyboard
class CodeBasedCollectionViewCell: UICollectionViewCell, Reusable {
// The rest of the cell code goes here
}And for a XIB-based UICollectionViewCell subclass:
// A UICollectionViewCell using a XIB to define it's UI
// And that will need to register using that XIB
class NibBasedCollectionViewCell: UICollectionViewCell, NibReusable {
// The rest of the cell code goes here
}Reusable can also be used to load an arbitrary UIView subclass (even a non-reusable, non-cell view) designed in a XIB by simply marking it as NibLoadable:
class NibBasedRandomView: UIView, NibLoadable {
// The rest of the view code goes here
}Reusable can also be used to load a UIView in Xib's File's Owner (even a non-reusable, non-cell view) designed in a XIB by simply marking it as NibOwnerLoadable:
class NibBasedRandomView: UIView, NibOwnerLoadable {
// The rest of the view code goes here
}Then to use those cells, you'll register them like this, without the need to manipulate any reuseIdentifier anywhere in the code:
class MyViewController: UIViewController {
@IBOutlet private weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.registerReusableCell(CodeBasedCustomCell) // This will register using the class without using a UINib
tableView.registerReusableCell(NibBasedCustomCell) // This will register using NibBasedCustomCell.xib
}
}Note: If your cell is prototyped in a Storyboard, there is no need to call
registerReusableCellfor those cell prototypes, as the Storyboard auto-register these cell prototypes with theUITableView/UICollectionViewthey are prototyped into.
Then in your implementation of UIViewControllerDataSource, especially to dequeue a cell you will be able to do:
extension MyViewController: UITableViewDataSource {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(indexPath: indexPath) as CodeBasedCustomCell
// Customize the cell here. You can call any type-specific methods here without the need for type-casting
cell.fillWithText("Foo")
return cell
} else {
let cell = tableView.dequeueReusableCell(indexPath: indexPath) as NibBasedCustomCell
// Customize the cell here. no need to downcasting here either!
cell.fillWithImage(UIImage(named:"Bar"))
return cell
}
}
}If you mark an arbitrary (non-cell) UIView as NibLoadable as demonstrated above, you can also instantiate such a view using its Nib by calling loadFromNib():
let instance1 = NibBasedRandomView.loadFromNib()
let instance2 = NibBasedRandomView.loadFromNib()
let instance3 = NibBasedRandomView.loadFromNib()
…If one of your custom UIViewController (named CustomVC for example) is designed as the initial ViewController of a Storyboard (named CustomVC.storyboard):
- simply mark it as conforming to
StoryboardBased - call
instantiate()to create an instance from the Storyboard
final class CustomVC: UIViewController: StoryboardBased { }
…
func presentIt() {
let vc = CustomVC.instantitate()
self.presentViewController(vc, animated: true) {}
}If your custom UIViewController (named SecondaryVC for example) is designed in a Storyboard CustomVC.storyboard but is not the initial ViewController, but instead has a custom "Scene Identifier" with the value SecondaryVC to be reached:
- mark it as conforming to
StoryboardSceneBased - define
static let storyboard = …to indicate the Storyboard where this scene is designed - call
instantiate()to create an instance from the Storyboard
(If you don't implement static var sceneIdentifier, it will assume the Scene to have the name of the class used as its scene identifier)
final class SecondaryVC: UIViewController: StoryboardSceneBased {
static let storyboard = UIStoryboard(name: "CustomVC", bundle: nil)
}
…
func presentIt() {
let vc = SecondaryVC.instantitate() // Init from the "SecondaryVC" scene of CustomVC.storyboard
self.presentViewController(vc, animated: true) {}
}It's strongly advised to mark your custom UITableViewCell, UICollectionViewCell, UIView and UIViewController subclasses as being final, because:
- usually your custom cells and VCs are not intended to be subclassed
- more importantly, it helps the compiler a lot and gives you big optimizations
- it can be required in some cases when conforming to
protocolsthat haveSelfrequirements, like the ones used by this pod (Reusable,StoryboardBased, …).
In some cases you can avoid making your classes final, but in general it's a good practice, and in the case of this pod, usually your custom UIViewController or whatever won't be subclassed anyway:
- Either they are intended to be used and instantiated directly and never be subclassed, so
finalmakes sense here - In case your custom
UIViewController,UITableViewCell, etc… is intended to be subclassed and be the parent class of many classes in your app, it makes more sense to add the protocol conformance (StoryboardBased,Reusable, …) to the child classes (and mark themfinal) than adding the protocol on the parent, abstract class.
Reusable, NibLoadable and NibReusable are what is usually called Mixins, which basically is a Swift protocol with a default implementation provided for all of its methods. The main benefit is that you don't need to add any code: just conform to Reusable, NibLoadable or NibReusable and you're ready to go.
But of course, those provided implementations are just default implementations. That means that if you need you can still provide your own implementations in case for some reason some of your cells don't follow the classic configuration of using the same name for both the class, the reuseIdentifier and the XIB file.
class VeryCustomNibBasedCell: UITableViewCell, NibReusable {
// This cell use a non-standard configuration: its reuseIdentifier and XIB file
// have a different name as the class itself. So we need to provide a custom implementation or `NibReusable`
static var reuseIdentifier: String { return "VeryCustomReuseIdentifier" }
static var nib: UINib { return UINib(nibName: "VeryCustomUI", bundle: nil) } // Use VeryCustomUI.xib
// Then continue with the rest of your normal cell code
}Note how, in the examples above and when using Reusable:
-
You never have to use a String-typed
reuseIdentifierin your code anywhere -
The proper cell type to dequeue is infered by Swift from the return type
- The simple fact that we wrote
dequeueReusableCell(…) as MyCellTypelet the Swift compiler infer that you expect aMyCellTypeand deduce thereuseIdentifierto use for that all by itself! ✨
- The simple fact that we wrote
-
The code is type-safe, as the
dequeueReusableCell(…)function will return the type you asked — and not theUITableViewCellnon-specific superclass as with Apple APIs- This way you can call methods even specific to your
MyCellTypereturn type on that cell next, without the need to cast it!
- This way you can call methods even specific to your
For more information and explanations on this code, see my detailed blog post about this here.
This repository comes with an example project in the Example/ folder. Feel free to try it.
It demonstrate how Reusable work:
- both for
UITableViewCellandUICollectionViewCellsubclasses, - both for cells whose UI template is either only provided by plain code, or provided by a XIB, or prototyped directly in a Storyboard.
- both for cells
UICollectionView'sSupplementaryViews(section Headers)
This code is distributed under the MIT license. See the LICENSE file for more info.

