DynamicStackView: A love/hate compromise

I have a love/hate relationship with UITableView. I also have a love/hate relationship with UIStackView. It’s like they’re perfect for the exact purpose they were built for, but once you start looking at custom solutions they’ll be quick to slap you in the face.

So, a while ago I was working on a chatbot of sorts. It had lots of nice animations and patterns that clearly would not work very well inside the confines of a UITableView. Also, the chat history was divided into chunks with question + answer, meaning I really only wanted a limited set of rows for each “Q&A”. This in itself would have been doable with multiple data sources, but the deallocation of non-visible cells was a dealbreaker. Instead I tried out a conceptual mix of both UITableView and UIStackView, dubbing it DynamicStackView.

While the concept itself is nothing new – adding views to a container programmatically – the application of UITableView mechanics to a UIStackView is perhaps a little less conventional. It gives us a simpler version of a UITableView while adding an otherwise nonexistent model-cell-table relationship to a UIStackView.

A quick rundown

Basically there are three parts to DynamicStackView:

  1. DynamicStackViewModel
    • Protocol for associating generic models with DynamicStackViewCells .
    • View for subclassing to get access to associated models.
    • Custom UIStackView containing all DynamicStackViewCells .

A simplified version of the cell creation method in our custom UIStackView (ie. DynamicStackView ) shows us the core idea of the model-cell-table relationship:

private func createCell(from model: DynamicStackViewModel) -> DynamicStackViewCell {
    let cell = model.cellType.init()
    cell.setup(model: model)
    return cell

Subclassing a DynamicStackViewCell gives us access to the setup method above, thereby also giving us access to the associated model. Then, by exposing accessor functions like…

public func append(model: DynamicStackViewModel) {
    let cell = createCell(from: model)

… it’s really convenient for the developer to simply add models to the DynamicStackView and have those delivered to the associated cell. The actual connection is handled by the DynamicStackViewModel protocol:

public protocol DynamicStackViewModel {
    var cellType: DynamicStackViewCell.Type { get }

This way, a nice and tidy extension to any generic model connects it to a subclassed DynamicStackViewCell , leaving just a tiny footprint on the code outside the framework.

An actual implementation

DynamicStackViewModel – Protocol

All models to be used with DynamicStackView has to adhere to this protocol. As stated earlier, you’ll be associating them with a DynamicStackViewCell to automatically generate views in the DynamicStackView for you:

extension Content: DynamicStackViewModel {
    var cellType: DynamicStackViewCell.Type {
        return ContentCell.self

DynamicStackViewCell – Superclass

By subclassing DynamicStackViewCell you can ( must ) override its setup method to get access to the associated DynamicStackViewModel :

override func setup(model: DynamicStackViewModel) {
    if let model = model as? Content {
        label.text = model.text

DynamicStackView – Container

Simply add a new IBOutlet :

@IBOutlet weak var dynamicStackView: DynamicStackView!

Now add your DynamicStackViewModel compatible models to it:

let content = Content(text: "My content")
dynamicStackView.append(model: content)

Or as an array:

let contentArray = [
    Content(text: "My content"),
    Content(text: "Some more content")
dynamicStackView.append(models: contentArray)

If you want to manually override the cell type, simply specify it when adding models:

let overriddenContent = Content(text: "Overridden content")
dynamicStackView.append(model: overriddenContent, cellType: OtherContentCell.self)

Note:When overriding the cell type, make sure that your subclassed DynamicStackViewCell can handle the new DynamicStackViewModel :

override func setup(model: DynamicStackViewModel) {
    if let model = model as? Content {
        label.text = model.text
    } else if let model = model as? OtherContent {
        label.text = model.text

Finally, you can get access to the tapped cell through a callback block:

dynamicStackView.didTapCell = { cell in


With a pretty modest line count we now have a quick and lightweight framework handling lists of views in an automated fashion. Want rows at the bottom instead? Simply adjust your constraints in Interface Builder. Want scroll? Wrap everything in a scroll view.

Granted, DynamicStackView won’t (and shouldn’t) replace a UITableView in most projects, but for smaller lists – or those edge case chatbots – I think it’s a decent alternative.

You can find the complete framework project at github/varvet/DynamicStackView , and a simplified example project to play with at github/varvet/DynamicStackView/Example .

稿源:Varvet (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 移动开发 » DynamicStackView: A love/hate compromise

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录