Replicating the Wallet TableView in iOS 12.2
17. March 2019
Update December 2019
Starting in iOS 13, there's now a new tableview style
.insetGrouped
which gives you the exact look (but
better lol) for free
Original Article
In iOS 12.2, Apple introduces a new look for the Wallet apps table views. They are inset by 16 points on the left and right and the top and bottom cells of each section are rounded which looks very nice (at least to me). So I wanted to replicate the look of it for my new app. This is not really meant to be tutorial, more a description of what I did to replicate the look.
The finished product will look like this: // Insert screenshot of finished tableview
First, I added a UINavigationController
to my empty
ViewController and connected it as root controller.
Then I added a UIScrollView
to the ViewController and
added constraints to the superview and set them all to 0. After that
I dragged a UITableView
into the scrollview and added
constraints as well. 16 points to the left and right and 0 to top
and bottom of the scroll view. I also had to constraint the width
and height of UITableView
. The reason I used a
UIScrollView
as a container for the table view, instead
of just setting the tableview's left and right constraint to the
superview to 16, is that that would have led to the user losing the
ability to scroll on the left and right edges of the screen. I also
set the tableview's style to grouped and the background color to
UIColor.clear
. I also disabled any scrolling on the
tableview because we don't want 'dual-scrolling' *smirk*
At first I wanted to experiment with how the cells behaved in sections with maybe only one or two cells, so I added some code to genereate a random amount of cells.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Returns at least 1
return Int(arc4random_uniform(2)) + 1
}
func numberOfSections(in tableView: UITableView) -> Int {
// Returns at least 2
return Int(arc4random_uniform(5)) + 2
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
// Displays the current section as header title
return "Section \(section)"
}
I also had to disable horizontal scrolling and set the background
color to something other than white because I wanted the cells to be
white. So in viewDidLoad:
I added:
self.view.backgroundColor = UIColor(white: 0.95, alpha: 1)
self.widthConstraint.constant = self.view.frame.width - (16 * 2)
Another thing that was necessary get the scrollview to behave and
display the tablview correctly was to set it's content size after
the tableview has been loaded for the first time. So in
viewDidAppear:
I called a new function
updateContentSize()
which looks like this:
func updateContentSize() {
// Add some padding on the bottom and constraint the width to the width of the view
self.scrollView.contentSize = CGSize(width: self.view.frame.width, height: self.tableView.contentSize.height + 20)
// Constraint the tableview's height to the size of its content
self.heightConstraint.constant = self.tableView.contentSize.height
self.view.layoutIfNeeded()
}
Now let's round the corners of those cells. For that I added a new
file UIView+Extensions.swift
and pasted in
my UIView extension
to round corners using expressive enum cases.
Then I created a new extension for UITableViewCell to quickly apply the rounding depending on its position in the section.
extension UITableViewCell {
func applyConfig(for indexPath: IndexPath, numberOfCellsInSection: Int) {
switch indexPath.row {
case numberOfCellsInSection - 1:
// This is the case when the cell is last in the section,
// so we round to bottom corners
self.roundCorners(.bottom, radius: 15)
// However, if it's the only one, round all four
if numberOfCellsInSection == 1 {
self.roundCorners(.all, radius: 15)
}
case 0:
// If the cell is first in the section, round the top corners
self.roundCorners(.top, radius: 15)
default:
// If it's somewhere in the middle, round no corners
self.roundCorners(.all, radius: 0)
}
}
}
Now the cells could be rounded adding the following code in
cellForRowAt:
:
cell.applyConfig(for: indexPath, numberOfCellsInSection: tableView.numberOfRows(inSection: indexPath.section))
But that procuced a table that looked like this:
So to fix that I set the tableviews separator color to
UIColor.clear
and instead added a new
CALayer
to the top of each cell.
func applyConfig(for indexPath: IndexPath, numberOfCellsInSection: Int) {
// ...
if indexPath.row != 0 {
let bottomBorder = CALayer()
bottomBorder.frame = CGRect(x: 16.0, y: 0, width: self.contentView.frame.size.width - 16, height: 0.2)
bottomBorder.backgroundColor = UIColor(white: 0.8, alpha: 1.0).cgColor
self.contentView.layer.addSublayer(bottomBorder)
}
}
Looking nice!
The code for this article is available on Github. If you liked this article or have any suggestions, you can send me an email. Anyways, thanks for reading :-)