Open-source project
younatics/ExpandableCell avatar
younatics/ExpandableCell

ExpandableCell: a Swift 6 UITableViewCell that expands without row surgery

✨ Awesome expandable, collapsible tableview cell for iOS written in Swift 5

766 stars123 forksSwiftMIT

At a glance

What is it?
ExpandableCell is a small iOS library that gives a UITableView expandable and collapsible rows, with the expanded cells supplied through a delegate instead of being inserted manually. It supports single, per-section and multi expansion styles, ships through Swift Package Manager and CocoaPods, and requires iOS 13 with Swift 6.
Who is it for?
ExpandableCell is worth taking if you are building an iOS table with drill-down rows and you would rather not manage insertRows and deleteRows by hand, since the delegate-driven model removes the index arithmetic that normally causes off-by-one bugs in expanded state.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 40 days ago.
What is it written in?
Mainly Swift, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem it names is insertRows and deleteRows

ExpandableCell is a Swift library for expandable and collapsible table view cells on iOS, and the README states the motivation in unusually plain terms: the library was made because insertRows and deleteRows is hard to use.

That is the right problem to name. Expanding a row in a plain UITableView means inserting new rows into the data source at the right index, keeping the section and row counts consistent, and doing the reverse when collapsing. Every one of those steps is a chance for the off-by-one error that makes a table behave inconsistently after a few expansions.

This library sidesteps the surgery. Instead of the table inserting and deleting rows, the expanded cells are returned from a delegate method and the library handles the state. The project is a refactor of an earlier library by the same author, YNExpandableCell, and the README describes the rewrite as more concise and bug free.

The scope is deliberately narrow. It is a cell plus a table view subclass plus a delegate protocol, all under the ExpandableCell module name. There is no view model layer, no diffing, no data source abstraction, and the cell itself is an ordinary UITableViewCell subclass that you can lay out however you like.

The delivery channels are the usual pair for a modern iOS library, with a badge for Swift Package Manager compatibility and a CocoaPods version badge. The platform floor is iOS 13.0 and above, and the project now uses Swift 6.

Two required delegate methods carry the whole contract

The protocol is ExpandableDelegate, and the README marks two of its methods as key. The first asks which cells to show when a row expands:

swift
func expandableTableView(_ expandableTableView: ExpandableTableView, expandedCellsForRowAt indexPath: IndexPath) -> [UITableViewCell]?

The second asks how tall those cells are:

swift
func expandableTableView(_ expandableTableView: ExpandableTableView, heightsForExpandedRowAt indexPath: IndexPath) -> [CGFloat]?

Both return optionals, and that is where the design puts the pressure on you. Return an empty array or nil and there is nothing to expand into. The two methods are paired because the height list has to line up with the cell list index for index, which means you are effectively maintaining two parallel arrays for every expandable row.

The other required methods are the ones you would write anyway. The data source still needs a cell for a row and a row count per section, and the delegate still needs a height for a row, all with the expandableTableView prefix on the first argument so the compiler can route them.

The optional methods are where the library leaves room for your own behaviour. There are two that fire on selecting an expanded row, one that gives you the index path and one that gives you the index path and the cell, and then the usual table view delegate passthroughs for selection, section headers, section counts and the willDisplay callbacks for cells and header and footer views.

The passthrough list is what makes this usable in a real app. You are not forced to reimplement table view behaviour in a separate protocol, you implement the standard UITableViewDelegate and UITableViewDataSource methods with a different first parameter and the library forwards the rest.

Three expansion styles cover the usual disclosure behaviours

The expansionStyle property on ExpandableTableView selects between three behaviours, and the descriptions in the README are precise about the difference.

The single style allows one row open at a time. The singlePerSection style allows one row open at a time per section, so a table with three sections can have three open rows as long as they are in different sections. The multi style allows any number of rows open at once.

This is the property to look at first when choosing the library, because it decides whether the disclosure behaviour matches your design. A settings list with grouped sections usually wants singlePerSection, a shopping cart with line items to reveal wants multi, and a flat list of records often wants single.

Two other properties tune the feel. The animation property takes a UITableView.RowAnimation and controls what happens when a row opens and closes. The autoRemoveSelection boolean controls the selection flash, and the README explains the trade-off directly: true makes the cell flicker selected, false applies the table view's own selection behaviour, single or multi.

That second one is worth understanding because the flicker is a symptom of the library re-rendering rows on expansion. If your design has no visible selection, autoRemoveSelection true is the setting that stops rows flashing during the animation.

There is also a small set of imperative methods on the table view. openAll opens everything the delegate has provided, closeAll closes it, reloadData reloads the table while keeping the expanded cells working, and open(at indexPath:) opens one specific row. Those four cover the cases where the expansion state has to change from code rather than from a tap.

The cell is a subclass you are expected to edit

ExpandableCell is an open UITableViewCell subclass, and the README shows three separate ways to modify it by inheriting from it, which is a design choice about where the library expects your changes to live.

For a disclosure arrow, you inherit ExpandableCell and work with the arrowImageView property:

swift
open class ExpandableCell: UITableViewCell {
    open var arrowImageView = UIImageView()
}

For the highlight animation, the same pattern applies with the highlightAnimation property, whose value is a HighlightAnimation enum with an animated case. For the spacing to the trailing edge, there is a trailingMargin property of type CGFloat, and the README notes the default margin is 16.

All three are declared open, so a subclass in your own module can override them. That means the library gives you the plumbing, the arrow view, the highlight behaviour and the spacing, and leaves the actual cell layout, labels and content to you.

The cell also exposes three query-style methods for your own use. isExpanded reports whether a cell is currently expanded, isInitiallyExpanded tells you to make the cell open when the table view content first appears, and isSelectable lets you decide whether a cell is selectable regardless of the table view's selectionStyle.

That last one is more than a convenience. A row that expands and contains buttons or controls should not also respond to a row tap, and isSelectable is how you suppress that per cell rather than by working around the table view's behaviour.

The animation style is set on the table view rather than the cell, with the assignment shown as a single line:

Swift
tableView.animation = .automatic

Two installation routes, and a major version jump to Swift 6

Installation is through Swift Package Manager or CocoaPods. In Xcode, the README directs you to choose File and then Add Package Dependencies and enter the repository URL:

code
https://github.com/younatics/ExpandableCell.git

The alternative for a project that manages its dependencies through CocoaPods is the podspec, and the repository ships ExpandableCell.podspec at the top level next to Package.swift, so both routes are first-class in the tree.

The requirements section is where the version history becomes relevant. The library uses Swift 6 and requires iOS 13 or later, supports Swift Package Manager with Swift tools 6, and CocoaPods with an iOS 13 deployment target.

The release list shows what that means for anyone coming from an older integration. The 2.0.0 release is labelled as Swift 6 modernization and dates from 2026-07-07. Before it, 1.3.0 added Swift 5.0 support in 2019 and 1.2.8 was an update due to a pull request in 2018. So there was a long stretch on the Swift 5 line, and moving to 2.0.0 means a language version bump rather than a routine dependency refresh.

For a project still on Swift 5, that is a migration with real friction, and the older tag remains the option. For a project already on Swift 6, the current release is the one to take.

The repository is not archived and the last push was on 2026-09-13, so the tree is being worked on, though the published release list stops in July. The project is MIT licensed with the LICENSE file at the root, which is the most permissive case and means the code can be dropped into a proprietary app without conditions.

Editorial conclusion

ExpandableCell is worth taking if you are building an iOS table with drill-down rows and you would rather not manage insertRows and deleteRows by hand, since the delegate-driven model removes the index arithmetic that normally causes off-by-one bugs in expanded state. It is the wrong tool if you need a section-header accordion with rich animation, if you are on a Swift 5 or iOS 12 codebase since version 2.0.0 moved the library to Swift 6, or if you need something beyond UITableView. Before you adopt it, verify three things yourself: that the two required delegate methods cover the heights your expanded cells need, since returning nil from them leaves the library with nothing to draw, that your deployment target meets the iOS 13 floor, and that the CocoaPods and Swift Package Manager routes give you the same version, since the library now carries both a Package.swift and an ExpandableCell.podspec.

Frequently asked questions

What problem does ExpandableCell solve in UITableView?

It removes the need to call insertRows and deleteRows yourself when a row expands. The expanded cells are returned from the ExpandableDelegate method expandedCellsForRowAt, and the library manages the state and the row bookkeeping.

Which delegate methods are required by ExpandableCell?

Two are marked as key: expandedCellsForRowAt, which returns the cells to show for an expanded row, and heightsForExpandedRowAt, which returns their heights as an array of CGFloat. You also implement the standard cellForRowAt, numberOfRowsInSection and heightForRowAt with the expandableTableView prefix.

How do I add ExpandableCell to an Xcode project?

Choose File, then Add Package Dependencies and enter the repository URL https://github.com/younatics/ExpandableCell.git, or use the CocoaPods podspec in the repository root. The library requires iOS 13 or later and Swift 6, with Swift tools 6 for Swift Package Manager.

Can I control how many rows expand at once in ExpandableCell?

Yes. The expansionStyle property takes single, where one row is open at a time, singlePerSection, where one row per section is open, or multi, where any number of rows stay open. The animation property sets the row animation used when opening and closing.

How do I change the disclosure arrow on ExpandableCell rows?

Inherit from ExpandableCell and work with the open arrowImageView property. The same pattern is used for highlightAnimation and for trailingMargin, which is a CGFloat with a default of 16.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. younatics/ExpandableCell on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/younatics-expandablecell.svg)](https://hysenlabs.com/projects/younatics-expandablecell)

Was this useful?