UITable
is the bread and butter of iOS apps.
This is as true today as it was with the first iPhone over a decade ago.
Back in those early days, developers worked hard to achieve smooth scroll performance — often resorting to extreme measures. For example, to achieve 60FPS on a table view with custom cells on an iPhone 3G you’d often have to draw text directly to a cell’s Core Graphics context, because compositing subviews was too slow. (Interface Builder? Auto Layout? Phooey! Back in my day, we calculated all of our view frames by hand — up hill, both ways, in the snow)
At the time, the highest praise a developer could receive for their efforts was to have someone describe their as “buttery”: smooth, responsive, without any jitter. And we milked that hardware for all it was worth to make that happen.
In honor of all the skeuomorphic sweat spilled
to transmute code into that most golden of dairy products,
and in the interest of maximizing the performance of our apps today,
we’ll turn our attention to a class that —
for many of us —
has been hiding in plain sight:
UITable
.
Introduced in iOS 6,
UITable
takes the reuse functionality of table view cells
and makes it available to section headers and footers.
You can either use it directly
or create a subclass to customize its appearance.
Now,
table views are responsible for a great deal of functionality,
and one can easily lose track of how all of its responsibilities are delegated.
So let’s start with a quick run-down of UITable
before going into more detail about UITable
:
UITableView Review
A UITable
consists of sections,
each of which containing a number of rows.
For each row,
the table view’s data
is responsible for returning a UITable
to represent each section / row index path
with the table
delegate method.
The table view’s data
may also provide
a title to be displayed in the header or footer of a section
by implementing the optional
table
and
table
delegate methods.
To customize the appearance of section headers or footers,
the table view’s delegate
can implement the optional delegate methods
table
and
table
.
To keep scroll performance snappy,
table views recycle their cells as they scroll out of view.
This process is known as cell reuse.
You can take advantage of reuse for section headers and footers,
by returning an instance of UITable
(or a subclass).
What better way to demonstrate this obscure technique for buttery performance create an app to display per-capita dairy product consumption statistics from the USDA? (That was a hypothetical question.)
For our example, we’ll keep our model nice and simple, with a nearly 1:1 mapping with the API we’ll use to display this information on a table view:
structSection{lettitle:Stringletimage:UIImageletrows:[(year:Int,amount:Decimal)]letnotes:String?}letbutter=Section(title:"Butter",image:#image Literal(resource Name:"Butter"),rows:[...],notes:nil)// etc.letsections:[Section]=[milk,yogurt,butter,cheese,cottage Cheese,condensed Milk,ice Cream,whey]
In the view controller itself,
the implementation for UITable
delegate methods
is cool and refreshing:
importUIKitfinalclassView Controller:UIView Controller{@IBOutletvartable View:UITable View!}// MARK: - UITable View Data SourceextensionView Controller:UITable View Data Source{funcnumber Of Sections(intable View:UITable View)->Int{returnsections.count}functable View(_table View:UITable View,number Of Rows In Sectionsection:Int)->Int{returnsections[section].rows.count}functable View(_table View:UITable View,cell For Row Atindex Path:Index Path)->UITable View Cell{letcell=table View.dequeue Reusable Cell(with Identifier:"Cell",for:index Path)letsection=sections[index Path.section]letrow=section.rows[index Path.row]cell.text Label?.text="\(row.year)"cell.detail Text Label?.text="\(row.amount)"returncell}}
Alright, let’s cut the cheese
and talk about the right wheyway
to use UITable
.
Creating a Section Header View
In this example,
we’ll offer two different approaches to
working with UITable
.
In the first, we’ll do everything in code; in the second, we’ll design things visually in Interface Builder. Feel free to adopt whichever one you prefer.
Option 1: Constructing the View Programmatically
Similar to UITable
each UITable
comes with
text
and detail
properties
that are lazily created and positioned within a content
.
As with cells,
you have the option to take or leave these built-in subviews
for your custom subclass.
For our example,
let’s use the existing text
and add an imageView along the trailing margin of the content
.
We do all of this in the designated initializer,
init(reuse
:
importUIKitfinalclassSection Header View:UITable View Header Footer View{staticletreuse Identifier:String=String(describing:self)varimage View:UIImage Viewoverrideinit(reuse Identifier:String?){super.init(reuse Identifier:reuse Identifier)image View=UIImage View()content View.add Subview(image View)image View.translates Autoresizing Mask Into Constraints=falseimage View.width Anchor.constraint(equal To Constant:24.0).is Active=trueimage View.height Anchor.constraint(equal To Constant:24.0).is Active=trueimage View.trailing Anchor.constraint(equal To:content View.layout Margins Guide.trailing Anchor).is Active=trueimage View.bottom Anchor.constraint(equal To:content View.layout Margins Guide.bottom Anchor).is Active=true}requiredinit?(codera Decoder:NSCoder){super.init(coder:a Decoder)}}
In our view controller,
we register the custom section header in view
by calling the register(_:for
method
on table
:
importUIKitfinalclassView Controller:UIView Controller{@IBOutletvartable View:UITable View!// MARK: UIView Controlleroverridefuncview Did Load(){super.view Did Load()self.table View.register(Section Header View.self,for Header Footer View Reuse Identifier:Section Header View.reuse Identifier)}}
Option 2: Designing the View in Interface Builder
Dynamic table view cells can be designed directly from a Storyboard, which can be quite convenient for prototyping interfaces. Unfortunately, at the time of writing, there is no documented way to design prototype section header / footer views as you can with table view cells.
However, we can still use Interface Builder to design our section header and footer views — all it takes a few extra steps.
First, create a new Swift file
that declares your UITable
subclass.
Next, create a new XIB file for your custom view:
In Interface Builder, navigate to the Identity Inspector in the Inspectors panel on the right-hand side, and set your subclass as the “Custom Class” for both File’s Owner and the top-level view.
Back in your subclass implementation,
declare an image
property
and an override to the existing text
property —
both with @IBOutlet
annotations —
and connect them to their counterparts in Interface Builder.
importUIKitfinalclassSection Header View:UITable View Header Footer View{staticletreuse Identifier:String=String(describing:self)staticvarnib:UINib{returnUINib(nib Name:String(describing:self),bundle:nil)}// Override `text Label` to add `@IBOutlet` annotation@IBOutletoverridevartext Label:UILabel?{get{return_text Label}set{_text Label=new Value}}privatevar_text Label:UILabel?@IBOutletvarimage View:UIImage View!}
Now, when you register your subclass for reuse with the table view controller,
pass a UINib
(provided here in a type property)
instead of Section
.
importUIKitfinalclassView Controller:UIView Controller{@IBOutletvartable View:UITable View!// MARK: UIView Controlleroverridefuncview Did Load(){super.view Did Load()self.table View.register(Section Header View.nib,for Header Footer View Reuse Identifier:Section Header View.reuse Identifier)}}
Implementing UITableViewDelegate Methods
From here,
it’s smooth scrollingsailing.
Enjoy your victory lap as you implement the requisite
UITable
methods:
importUIKit// MARK: - UITable View DelegateextensionView Controller:UITable View Delegate{functable View(_table View:UITable View,title For Header In Sectionsection:Int)->String?{returnsections[section].title}functable View(_table View:UITable View,title For Footer In Sectionsection:Int)->String?{returnsections[section].notes}functable View(_table View:UITable View,view For Header In Sectionsection:Int)->UIView?{guardletview=table View.dequeue Reusable Header Footer View(with Identifier:Section Header View.reuse Identifier)as?Section Header Viewelse{returnnil}view.text Label?.text=sections[section].titleview.image View?.image=sections[section].imagereturnview}}
With today’s comparatively over-powered iOS hardware, such proactive measures may well be unnecessary for achieving buttery smooth interactions.
But for those of your with demanding performance requirements,
for anyone yearning to be in the 2%,
to achieve the crème de la crème of responsive interfaces,
UITable
can be a great way to skim some fat off your code.
If nothing else,
its restrained, familiar API allows UITable
to be added to most codebases without introducing much churn.