Reusable View Layout using Protocol & MVVM
Idea đź’ˇ
The reason I have titled it “Reusable View Layout..” rather than simply “Reusable View..” because I’m NOT referring to object level reusability of views here. Rather I’ll be discussing about how we can reuse a view layout with the help of a protocol
& MVVM
. In the process I’ll share how we can outline a view’s interface, basically different configuration states, with a protocol
which will enable us to inject different view-models
into a view. In other words, the idea here is to design data agnostic modelling of a view layout.
MVVM
Before we move further, let’s get few things straight about MVVM
. It’s one of most confusing design patterns out there so just want to make sure we are on the same page. It’s a design pattern where we make a view dependent on an intermediate model object called view-model. This view-model, gets some data (doesn't matter from network or persistence layer) and apply some decorative methods which is finally consumed by a view. E.g. if we have a profile view class –– ProfileView
, we can create it’s view-model ProfileViewModel
to configure it and save it's state.
class ProfileView: UIView {
var nameLabel: UILabel!
func configure(with model: ProfileViewModel) {
nameLabel.text = model.nameTitle
}
}
struct ProfileViewModel {
var nameTitle: String { return "Profile Title" }
}
Binding
As we can see ProfileView
gets coupled with ProfileViewModel
for any kind of configuration which happens when you use MVVM
. This works well if your view, in the entire lifetime, will only have one kind of view-model. Now let's say we want to reuse ProfileView
class which should be capable of showing both an Owner
profile & a Guest
profile.
Enum
To support this, we can convert ProfileViewModel
into an enum where owner & guest will be different cases.
enum ProfileViewModel {
case owner
case guest
var nameTitle: String {
switch self {
case .owner: return "Hi, Owner"
case .guest: return "Hi, Guest"
}
}
}
This solution doesn’t look bad at once but if we keep on adding more cases, it will eventually become a God class. To avoid this we can modify the way we think about MVVM. Instead of making our view directly dependent on a view-model, I propose to make an interface
of a view which could include all the configurable properties. Thus we need something which a view could understand without actually knowing anything about it’s incoming view-models.
Protocol
To achieve that “something”, we can use a protocol
. A view could have an interface which could be outlined using a protocol. This interface could have all the configurable states of it’s view. Finally we can have different view-models conforming to it. This approach makes the view totally oblivious of the kinds of view-models it could be expecting. E.g,
class ProfileView: UIView {
...
func configure(with interface: ProfileViewInterface) {
nameLabel.text = interface.nameTitle
}
}
protocol ProfileViewInterface {
var nameTitle: String { get }
}
Thus now ProfileView
is only aware of the fact that some object will configure it and it will be conforming to ProfileViewInterface
. Doing so we have separated the layer between view and it's view-models. View is oblivious of it's view-models but view-models know which view they need to configure. This provides a scalable solution since different classes can now conform to ProfileViewInterface
so that they can act like view-models for their view. E.g,
class OwnerViewModel: ProfileViewInterface {
var nameTitle: String { return "Owner Title" }
}
class GuestViewModel: ProfileViewInterface {
var nameTitle: String { return "Guest Title" }
}
class AdminViewModel: ProfileViewInterface {
var nameTitle: String { return "Admin Title" }
}
let view = ProfileView()
let ownerModel = OwnerViewModel()
let guestModel = GuestViewModel()
let adminModel = AdminViewModel()view.configure(...)// here we can pass any of the three above view-models since they all conform to ProfileViewInterface
So tomorrow if you intend to add another kind of profile view then simply make a new view-model class conforming to ProfileViewInterface
and inject it to ProfileView
’s configure method 🚀.