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
UITableViewCell
classes to conform to eitherReusable
orNibReusable
(no additional code to implement!) - Then simply use
tableView.dequeueReusableCell(indexPath: indexPath) as MyCustomCell
to get a dequeued instance of the expected cell class. No need for you to manipulatereuseIdentifiers
manually! - 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
UIView
custom 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
UIViewController
custom 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
Reusable
protocol if they don't depend on a NIB (this will useregisterClass(…)
to register the cell) - the
NibReusable
protocol if they use aXIB
file 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
registerReusableCell
for those cell prototypes, as the Storyboard auto-register these cell prototypes with theUITableView
/UICollectionView
they 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
protocols
that haveSelf
requirements, 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
final
makes 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
reuseIdentifier
in 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 MyCellType
let the Swift compiler infer that you expect aMyCellType
and deduce thereuseIdentifier
to 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 theUITableViewCell
non-specific superclass as with Apple APIs- This way you can call methods even specific to your
MyCellType
return 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
UITableViewCell
andUICollectionViewCell
subclasses, - 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.