Sometimes you have to bind the same kind of objects with multiple views, like UIViewController, UITableViewCell, or UICollectionViewCell. Very often, the binding code is quite similar in all the views, so it would be a great idea if you can reuse it. We will use Swift protocol and protocol extensions to do it.
Let’s suppose we have a User
class:
class User {
var name = ""
var email = ""
var bio = ""
var image: UIImage? = nil
init(name: String, email: String, bio: String) {
self.name = name
self.email = email
self.bio = bio
}
}
We will create a protocol which will adopt all the interfaces that we want to bind with our User
instances. We will call it UserBindable
.
protocol UserBindable: AnyObject {
var user: User? { get set }
var nameLabel: UILabel! { get }
var emailLabel: UILabel! { get }
var bioLabel: UILabel! { get }
var imageView: UIImageView! { get }
}
The first var user
is the user to bind, and all the others are UIView
subclasses that we can use to bind the user.
Then, we create a protocol extension:
extension UserBindable {
// Make the views optionals
var nameLabel: UILabel! {
return nil
}
var emailLabel: UILabel! {
return nil
}
var bioLabel: UILabel! {
return nil
}
var imageView: UIImageView! {
return nil
}
// Bind
func bind(user: User) {
self.user = user
bind()
}
func bind() {
guard let user = self.user else {
return
}
if let nameLabel = self.nameLabel {
nameLabel.text = user.name
}
if let bioLabel = self.bioLabel {
bioLabel.text = user.bio
}
if let emailLabel = self.emailLabel {
emailLabel.text = user.email
}
if let imageView = self.imageView {
imageView.image = user.image
}
}
}
Here, we split the extension into two sections:
- The first one is used to provide a default value (
nil
) for all views, so the objects that will implement the protocol won’t need to have all of them. For instance, some of our views could exclude the image
, but some others can include it.
- We take the values of the user properties and set them to views.
Now, most of the job is done. If we have to present a user list, we will want to create a UITableViewCell
. Let`s imagine that this cell just needs to show the name and the email:
class UserTableViewCell: UITableViewCell, UserBindable {
var user: User?
// we can set the labels in interface builder or with by code.
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var emailLabel: UILabel!
}
And that’s all. Our cell implements UserBindable
, so it knows how to bind its interface to a user object. In our UITableViewDataSource
we can do:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! UserTableViewCell
let user = // find the user from your array or whatever
cell.bind(user)
return cell
}
If we want to show the detail of this user after touching it, we can have a view controller like this:
class UserDetailViewController: UIViewController, UserBindable {
var user: User?
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var emailLabel: UILabel!
@IBOutlet weak var bioLabel: UILabel!
@IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// here we suppose that we have set the user value before the viewDidLoad
bind()
}
}
As you can see, all the binding code is reused nicely.