Home

Awesome

Reusable

<p align="center"> <img alt="Reusable" src="Logo.png" width="150" height="150"/> </p>

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()

CircleCI Platform Version Language: Swift 3 Language: Swift 4 Language: Swift 5

Installation

<details><summary><b>Requirements</b>: which Reusable version to use for each Swift Version?</summary>
Swift VersionReusable Version
2.2 & 2.32.5.1
3.0 (†)3.0.0 +
4.04.0.2 +
5.04.1.0 +

(†) The Reusable 3.0 code also compiles with Swift 4, you'll need 4.0.2+ only if you're using Carthage for integration

</details>

Reusable can be integrated to your Xcode projects using one of the following options:

<details><summary>Installation instructions for <b>Swift Package Manager (SPM)</b></summary>

Swift Package Manager is Apple's decentralized dependency manager to integrate libraries to your Swift projects. It is now fully integrated with Xcode 11

To integrate Reusable into your project using SPM, specify it in your Package.swift file:

let package = Package(
    …
    dependencies: [
        .package(url: "https://github.com/AliSoftware/Reusable.git", from: "4.1.0"),
    ],
    targets: [
        .target(name: "YourTarget", dependencies: ["Reusable", …])
        …
    ]
)
</details> <details><summary>Installation instructions for <b>Carthage</b></summary>

Carthage is a decentralized dependency manager to add pre-built frameworks to your Cocoa application.

To integrate Reusable into your Xcode project using Carthage, specify it in your Cartfile:

github "AliSoftware/Reusable"

Then run carthage update --use-xcframeworks

</details> <details><summary>Installation instructions for <b>CocoaPods</b></summary>

CocoaPods is a dependency manager to automate integration of frameworks to your Swift and Objective-C Cocoa projects.

To integrate Reusable into your Xcode project using Cocoapods, specify it in your Podfile:

pod 'Reusable'
</details>

Introduction

This library aims to make it super-easy to create, dequeue and instantiate reusable views anywhere this pattern is used: from the obvious UITableViewCell and UICollectionViewCell to custom UIViews, even supporting UIViewControllers from Storyboards.
All of that simply by marking your classes as conforming to a protocol, without having to add any code, and creating a type-safe API with no more String-based API.

// Example of what Reusable allows you to do
final class MyCustomCell: UITableViewCell, Reusable { /* And that's it! */ }
tableView.register(cellType: MyCustomCell.self)
let cell: MyCustomCell = tableView.dequeueReusableCell(for: indexPath)

This concept, called a Mixin (a protocol with default implementation for all its methods), is explained here in my blog post in details.

Table of Contents


Type-safe UITableViewCell / UICollectionViewCell

✍️ Examples and explanations below use UITableView and UITableViewCell, but the exact same examples and explanations apply for UICollectionView and UICollectionViewCell.

1. Declare your cells to conform to Reusable or NibReusable

final class CustomCell: UITableViewCell, Reusable { /* And that's it! */ }

✍️ Notes

<details> <summary>📑 Example for a Code-based custom tableView cell</summary>
final class CodeBasedCustomCell: UITableViewCell, Reusable {
  // By default this cell will have a reuseIdentifier of "CodeBasedCustomCell"
  // unless you provide an alternative implementation of `static 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 }
}
</details> <details> <summary>📑 Example for a Nib-based custom tableView cell</summary>
final class NibBasedCustomCell: UITableViewCell, NibReusable {
// or
// final class NibBasedCustomCell: UITableViewCell, Reusable, NibLoadable {
  
  // Here we provide a nib for this cell class (which, if we don't override the protocol's
  // default implementation of `static var nib: UINib`, 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 }
}
</details> <details> <summary>📑 Example for a Code-based custom collectionView cell</summary>
// A UICollectionViewCell which doesn't need a XIB to register
// Either because it's all-code, or because it's registered via Storyboard
final class CodeBasedCollectionViewCell: UICollectionViewCell, Reusable {
  // The rest of the cell code goes here
}
</details> <details> <summary>📑 Example for a Nib-based custom collectionView cell</summary>
// A UICollectionViewCell using a XIB to define it's UI
// And that will need to register using that XIB
final class NibBasedCollectionViewCell: UICollectionViewCell, NibReusable {
// or
// final class NibBasedCollectionViewCell: UICollectionViewCell, Reusable, NibLoadable {
  
  // The rest of the cell code goes here
  
}
</details>

2. Register your cells

Unless you've prototyped your cell in a Storyboard, you'll have to register the cell class or Nib by code.

To do this, instead of calling registerClass(…) or registerNib(…) using a String-based reuseIdentifier, just call:

tableView.register(cellType: theCellClass.self)
<details> <summary>📑 Example of `UITableView` registration</summary>
class MyViewController: UIViewController {
  @IBOutlet private weak var tableView: UITableView!
  
  override func viewDidLoad() {
    super.viewDidLoad()
    // This will register using the class (via `register(AnyClass?, forCellReuseIdentifier: String)`)
    // because the CodeBasedCustomCell type conforms to Reusable, but not NibLoadable (nor the NibReusable typealias)
    tableView.register(cellType: CodeBasedCustomCell.self)
    // This will register using NibBasedCustomCell.xib (via `register(UINib?, forCellReuseIdentifier: String)`)
    // because the NibBasedCustomCell type conforms to NibLoadable (via the NibReusable typealias)
    tableView.register(cellType: NibBasedCustomCell.self)
  }
}
</details>

3. Dequeue your cells

To dequeue a cell (typically in your cellForRowAtIndexPath implementation), simply call dequeueReusableCell(indexPath:):

// Either
let cell = tableView.dequeueReusableCell(for: indexPath) as MyCustomCell
// Or
let cell: MyCustomCell = tableView.dequeueReusableCell(for: indexPath)

As long as Swift can use type-inference to understand that you'll want a cell of type MyCustomCell (either using as MyCustomCell or explicitly typing the receiving variable cell: MyCustomCell), it will magically infer both the cell class to use and thus its reuseIdentifier needed to dequeue the cell, and which exact type to return to save you a type-cast.

<details> <summary>📑 Example implementation of `cellForRowAtIndexPath` using `Reusable`</summary>
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
    }
  }
}
</details>

Now all you have is a beautiful code and type-safe cells, with compile-type checking, and no more String-based API!

💡 If the cell class you want to dequeue is computed at runtime and stored in a variable, you won't be able to use as theVariable or let cell: theVariable obviously. Instead, you can use the optional parameter cellType (which otherwise gets infered by the return type and is thus not necessary to provide explicitly)

<details> <summary>📑 Example with a cell type determined at runtime</summary>
class ParentCell: UITableViewCell, Reusable {}
class Child1Cell: ParentCell {}
class Child2Cell: ParentCell {}

func cellType(for indexPath: NSIndexPath) -> ParentCell.Type {
  return indexPath.row.isMultiple(of: 2) ? Child1Cell.self : Child2Cell.self
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cellClass = self.cellType(for: indexPath)
  // As `self.cellType(for:)` always returns a `ParentCell` (sub-)class, the type
  // of the variable `cell` below is infered to be `ParentCell` too. So only methods
  // declared in the parent `ParentCell` class will be accessible on the `cell` variable.
  // But this code will still dequeue the proper type of cell (Child1Cell or Child2Cell).
  let cell = tableView.dequeueReusableCell(for: indexPath, cellType: cellClass)
  // Then fill the content of your cell (using methods/properties from `ParentCell` type)
  return cell  
}
</details>

Type-safe XIB-based reusable views

Reusable also allows you to create reusable custom views designed in Interface Builder to reuse them in other XIBs or Storyboards, or by code. This allows you to treat those views like custom UI widgets that can be used in multiple places in your app.

1. Declare your views to conform to NibLoadable or NibOwnerLoadable

In your swift source declaring your custom view class:

// a XIB-based custom UIView, used as root of the XIB
final class NibBasedRootView: UIView, NibLoadable { /* and that's it! */ }

// a XIB-based custom UIView, used as the XIB's "File's Owner"
final class NibBasedFileOwnerView: UIView, NibOwnerLoadable { /* and that's it! */ }

💡 You should use the second approach if you plan to use your custom view in another XIB or Storyboard.
This will allow you to just drop a UIView in a XIB/Storyboard and change its class in IB's inspector to the class of your custom XIB-based view to use it. That custom view will then automagically load its own content from the associated XIB when instantiated by the storyboard containing it, without having to write additional code to load the content of the custom view manually every time.

2. Design your view in Interface Builder

For example if you named your class MyCustomWidget and made it NibOwnerLoadable:

<details> <summary>🖼📑 A view configured to be `NibOwnerLoadable`</summary>

NibOwnerLoadable view in Interface Builder

final class MyCustomWidget: UIView, NibOwnerLoadable {
  @IBOutlet private var rectView: UIView!
  @IBOutlet private var textLabel: UILabel!

  @IBInspectable var rectColor: UIColor? {
    didSet {
      self.rectView.backgroundColor = self.rectColor
    }
  }
  @IBInspectable var text: String? {
    didSet {
      self.textLabel.text = self.text
    }
  }
…
}
</details>

Then that widget can be integrated in a Storyboard Scene (or any other XIB) by simply dropping a UIView on the Storyboard, and changing its class to MyCustomWidget in IB's inspector.

<details> <summary>🖼 Example of a `NibOwnerLoadable` custom view once integrated in another Storyboard</summary>

NibOwnerLoadable integrated in a Storyboard

</details>

3a. Auto-loading the content of a NibOwnerLoadable view

If you used NibOwnerLoadable and made your custom view the File's Owner of your XIB, you should then override init?(coder:) so that it loads it's associated XIB as subviews and add constraints automatically:

final class MyCustomWidget: UIView, NibOwnerLoadable {
  …
  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    self.loadNibContent()
  }
}

self.loadNibContent() is a method provided by the NibOwnerLoadable mixin. It basically loads the content from the associated MyCustomWidget.xib, then add all the root views in that XIB as subviews of your MyCustomWidget, with appropriate layout constraints to make them the same size as your MyCustomWidget container view.

Overriding init?(coder:) and calling self.loadNibContent() thus allows you to have that content automatically loaded by the system when that MyCustomWidget in included in another XIB or in a Storyboard (as init?(coder:) is the init that is called by iOS to create those instances in a XIB or Storyboard)

💡 Note: it is also possible to override init(frame:) similarly, in order to be able to also create an instance of that view manually via code if needed.

3b. Instantiating a NibLoadable view

If you used NibLoadable and made your custom view the root view of your XIB (not using the File's Owner at all), these are not designed to be used in other Storyboards or XIBs like NibOwnerLoadable is, as they won't be able to auto-load their content.

Instead, you will instantiate those NibLoadable views by code, which is as simple as calling loadFromNib() on your custom class:

let view1 = NibBasedRootView.loadFromNib() // Create one instance
let view2 = NibBasedRootView.loadFromNib() // Create another one
let view3 = NibBasedRootView.loadFromNib() // and another one
…

Type-safe ViewControllers from Storyboards

Reusable also allows you to mark your UIViewController classes as StoryboardBased or StoryboardSceneBased to easily instantiate them from their associated Storyboard in a type-safe way.

1. Declare your UIViewController to conform to StoryboardBased or StoryboardSceneBased

In your swift source declaring your custom UIViewController class:

<details> <summary>📑 Example of a ViewController being the initial ViewController of its Storyboard</summary>

In this example, CustomVC is designed as the initial ViewController of a Storyboard named CustomVC.storyboard:

final class CustomVC: UIViewController, StoryboardBased { /* and that's it! */ }
</details> <details> <summary>📑 Example of a ViewController being an arbitrary scene in a differently-named Storyboard</summary>

In this example, SecondaryVC is designed in a Storyboard name CustomVC.storyboard (so with a different name than the class itself) and is not the initial ViewController, but instead has its "Scene Identifier" set to the value "SecondaryVC" (same as the class name)

Conforming to StoryboardSceneBased will still require you to implement static var sceneStoryboard: UIStoryboard { get } to indicate the Storyboard where this scene is designed. You can typically implement that property using a let type constant:

final class SecondaryVC: UIViewController, StoryboardSceneBased {
  static let sceneStoryboard = UIStoryboard(name: "CustomVC", bundle: nil)
  /* and that's it! */
}
</details>

2. Instantiate your UIViewControllers

Simply call instantiate() on your custom class. This will automatically know which storyboard to load it from, and which scene (initial or not) to use to instantiate it.

func presentSecondary() {
  let vc = SecondaryVC.instantiate() // Init from the "SecondaryVC" scene of CustomVC.storyboard
  self.present(vc, animated: true) {}
}

Additional tips

Make your subclasses final

I advise you to mark your custom UITableViewCell, UICollectionViewCell, UIView and UIViewController subclasses as being final. This is because:

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:

Customize reuseIdentifier, nib, etc for non-conventional uses

The protocols in this pod, like Reusable, NibLoadable, NibOwnerLoadable, StoryboardBased, 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, NibOwnerLoadable or any of those protocol and you're ready to go with no additional code to write.

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.

final 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 
}

The same is true for all the protocols of this pod, which always provide default implementations which could still be replaced by your own if you need some custom cases.

But the beauty is in 90% of cases the default implementation will match typical conventions and the default implementations will be exactly what you want!

Type-safety and fatalError

Reusable allows you to manipulate type-safe APIs and make you avoid typos. But things could still go wrong in case of a misconfguration, for example if you forgot to set the reuseIdentifier of your cell in its XIB, or you declared a FooViewController to be StoryboardBased but forgot to set the initial ViewController flag on that FooViewController scene in that Storyboard, etc.

In such cases, because those are developer errors that should be caught as early as possible in the development process, Reusable will call fatalError with an error message as descriptive as possible (instead of crashing with an obscure message about some force-cast or force-unwrap or whatnot) to help you configure it right.

For example, if Reusable fails to dequeue a cell, it will bail with a message like:

« Failed to dequeue a cell with identifier \(cellType.reuseIdentifier) matching type \(cellType.self). Check that the reuseIdentifier is set properly in your XIB/Storyboard and that you registered the cell beforehand. »

Hopefully, those explicit failure messages will allow you to understand what was misconfigured and help you fix it!


Example Project

This repository comes with an example project in the Example/ folder. Feel free to try it.

It demonstrates how Reusable works for:

Talks and Articles about Reusable

The concepts behind Reusable has been presented in various articles and talks:

License

This code is distributed under the MIT license. See the LICENSE file for more info.