How to display different type of cells in a UITableview in swift?
Recently I had a project where I have to display different types of components as a listing. At the beginning the project requirements was to have a simple setup. Maximum 2 component per view.
Yeah easy enough. I created custom xib files per component so I can load them up, mix them up whenever I want. By the way the overall layout was controlled by the API server so the design will be sent out from the server, so I have to layout components accordingly.
If anyone wondering what a component is, below image is will give you an idea.
Since we can control the interface from the API they added more and more component in to the layout. This is where I started to realize the design flow.
The more components we added, the system started to lose the performance. It not only decreases the performance but also started the crash the app whenever user switch the orientation.
At this point I realized I need to come up with a way to reuse the components instead of displaying all of them at once.
This is where the table view came into the rescue.
Beforehand, here is the output so you can know this is the right article for you or not without reading the whole article and waste your valuable research time :)
Before going ahead with the table view, I wanted to stick to following rules.
1. System design should allow future API changes quickly to avoid complete revamp in the future.
2. Components (classes) should allow any types of data to be displayed and properly match with the type of the API component.
3. Allow any number of components in the feed.
4. Views should adapt to orientation changes without crash.
5. Most importantly easily add new components to the list.
After a several trial and error with different approaches, finally I came up with a pretty good approach to handle the data with all the rules above.
First, I created a base model (protocol) which all the component models should implement. I will explain why we need that when I explain my data source class.
Base model was look like this,
protocol BaseViewModel {
var componentIndex: Int { get }
var componentCode: FeedComponentCodes { get }
}
I really like enums. For me it is more readable and less mistakes. So, I decided to have an enum to represent all the component types (componentCode). This make sure the point I mentioned earlier about the API component types should match with the component classes.
enum FeedComponentCodes: String {
case TopImageCard
case ThumbImageCard
case ScrollCard
case Unknown
}
Then I created models for each component implementing the base protocol,
ArticleInfo is a simple struct with all the data I needed to display in the table cell.
struct ArticleInfo {
var articleID: String
var title: String
var image: String
}
There are several advantages in this implementation,
1. It forces to have both componentIndex and component code in every component class.
2. Each component can have its own private data.
3. We can know the exact type of the component data that resides in each component class. (ex: ArticleInfo)
Now the component classes are done only thing pending for us is the data source which will bind the components with the table view.
Before explaining that I want to take a little detour to show you about the API and parsing.
{
"components": [
{
"componentCode": "TopImageCard",
"articles": [
{
"articleID": "001",
"title": "Game of Thrones producers to adapt Chinese sci-fi 'The Three-Body Problem' for Netflix",
"image": "articleImage"
},
{
"articleID": "002",
"title": "Intel has a new logo and jingle",
"image": "articleImage"
},
{
"articleID": "003",
"title": "Left behind because of Covid-19, a tiny dog travels 10,000 miles to rejoin her owners",
"image": "articleImage"
}
],
"authors": []
}
]
}
Above Json structure will give you a better understanding about the API call response. Its start with a parent “components” object. It’s an array so it can have any number of components. Single component will always have a “componentCode” to specify the type of the component. That’s the only required field and the rest can be adjusted according to the data you wish to display in that component.
Once you parse this feed you will be end up with an array of component objects which we can pass into our data source to generate the proper list of components that we can use to generate the table view. I will not go through the parsing the API to keep this article short but I will provide the complete code so you can understand it fully.
Below is the data source class which do the most heavy lifting.
This data source class object will be created by passing the API components array.
self.dataSource = FeedDataSource(components: fullComponentsFromAPI)
If you look though the code you will right away understand the importance of the base model.
var items = [BaseViewModel]()
This will ensure all the different components can be hold in one single array even though they are different types. (Because they all implement the BaseViewModel protocol)
private static func getComponentEnum(code: String) -> FeedComponentCodes? {
let compCode = code.trimmingCharacters(in: .whitespacesAndNewlines)
return FeedComponentCodes(rawValue: compCode)
}
This little handy function will convert the API component type into relevant enum type, so we don’t have to work with string types.
First we have to loop through all the API components so we can create proper models for each component.
for (i, singleComponent) in components.enumerated() {
}
Next we create the enum from the API component code (String) and make sure that component code is supported from our system.
let code = singleComponent.componentCode
guard let componentCode = FeedDataSource.getComponentEnum(code: code) else { continue }
Then switch through all the components and generate proper model for each component and add them to our main items array.
switch componentCode {
case .TopImageCard:
…..
}
* ThumbImageCard, ScrollCard that mention in the above code are also a different type of components and you can see them in the sample code provided below.
Now the data source is ready we only have two things to do.
1. Create table view cells for each component
2. Display the relevant cell according to the component type
Creating table view cells is depend on the design you wish to present the data so I will not go through it since its unique for your requirements.
Let’s see how we can display our cells,
There are several important points to consider from the above code.
1. We have full list of components from the data source right away so we can easily pick the relevant component item according to the indexpath.
if let item = self.dataSource?.items[indexPath.row]
2. We can know the component type right away without guessing the type against all possible options
switch item.componentCode {
case .TopImageCard:
…..
}
3. Once we know the component type we can type cast the list items into proper types because we control the type from the models. This also make sure that we are handling the correct type of data from the table view cell classes.
let listData = item as! TopImageCardModel
4. Finally, we assign and return the proper cell class for display.
cell = customCell
return cell
This design really helps me to achieve all the points I mentioned at the beginning of the article as well as help me to add and remove any type of components easily.
You can download the complete code from github,
https://github.com/niroshanf/DifferentTableviewCells
I really like to know your thoughts so please send me any comments you have.
Until next time.. happy coding
Comments
Post a Comment