Recreating the iOS 12.2 Wallet table view
February 2, 2019
UPDATE: This feature is now built-in starting with iOS 13 via the insetGrouped
table view style!
The Wallet app was redesigned in iOS 12.2 with a fresher, more rounded look. This is most prominent in the table views scattered throughout the app — sections are rounded at the edges, similar how a detail table view looks on iPad. It looks really good everywhere, though, so let’s try to recreate it!
(TL;DR: Here’s the code.)
I’ll be using my app SuperHomework for this, but of course the code should work on any iOS app. I’m also using the iOS 12.2 beta (it should work on any iOS 12.x release, though) and Swift 4.2, the latest versions at the time of writing. I should also note that part of this implementation was taken from this answer and part of this answer on Stack Overflow.
Let’s get started! The first thing you’ll notice is how the section insets are increased in the Wallet app. I was experiencing issues setting UITableView.contentInset
(which you would most likely want to do if you can get it working), so instead I just applied constraints to the left and right edges of the table view using SnapKit and set the background color of the main view to that of the table view, giving a seamless look.
self.tableView.snp.remakeConstraints { make in
make.top.bottom.equalToSuperview()
make.left.right.equalToSuperview().inset(16)
}
self.view.backgroundColor = self.tableView.backgroundColor
Next, I created a UITableView
extension to make the following implementation easier to apply to many different table views.
extension UITableView {
func useRoundedSectionCorners() {
// This will be called in `viewDidLoad()` to set up the table view
}
func display(withRoundedSectionCorners cell: UITableViewCell, at indexPath: IndexPath) {
// This will be called in `tableView(_:willDisplay:forRowAt:)` to render each cell
}
}
Inside the useRoundedSectionCorners
method, I disabled the default separator line that runs through all of the cells and adds a section border — we’ll write our own separator in a bit. I also removed all of the excess padding around the edges of the table view that section headers/footers use to indent themselves.
func useRoundedSectionCorners() {
self.separatorStyle = .none
self.separatorInset = UIEdgeInsets(top: 0, left: self.separatorInset.left, bottom: 0, right: 0)
}
Now let’s jump into display(withRoundedSectionCorners:at:)
. The first thing we need to do is determine which modifications to make on the cell — that is, we don’t want to round the corners of a cell that’s in the middle of a section and whatnot. We can do that by passing the cell’s indexPath
to the method and doing some math:
func display(withRoundedSectionCorners cell: UITableViewCell, at indexPath: IndexPath) {
// Determine what modifications to make
let numberOfRowsInSection = self.numberOfRows(inSection: indexPath.section)
var shouldRoundTop = false
var shouldRoundBottom = false
if indexPath.row == 0 && indexPath.row == numberOfRowsInSection - 1 {
// the cell is the only one in the section
shouldRoundTop = true
shouldRoundBottom = true
} else if indexPath.row == 0 {
// the cell is the first in the section
shouldRoundTop = true
} else if indexPath.row == numberOfRowsInSection - 1 {
// the cell is the last in the section
shouldRoundBottom = true
}
}
Next, we’ll round the corners of the cell based on the calculations we just did. This is achieved using a UIBezierPath
with our desired corner radius (12pt in this case to match the Wallet app).
func display(withRoundedSectionCorners cell: UITableViewCell, at indexPath: IndexPath) {
// Determine what modifications to make
// ...
// Round corners if applicable
if shouldRoundTop && shouldRoundBottom {
cell.layer.cornerRadius = 10
cell.layer.masksToBounds = true
} else if shouldRoundTop || shouldRoundBottom {
let shape = CAShapeLayer()
let rect = CGRect(x: 0, y: 0, width: cell.bounds.width, height: cell.bounds.size.height)
let corners: UIRectCorner = shouldRoundTop ? [.topLeft, .topRight] : [.bottomRight, .bottomLeft]
shape.path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: 12, height: 12)).cgPath
cell.layer.mask = shape
cell.layer.masksToBounds = true
}
}
Finally, we need to draw our own separator line for cells that are in the middle of a section, if the section has multiple rows.
func display(withRoundedSectionCorners cell: UITableViewCell, at indexPath: IndexPath) {
// Determine what modifications to make
// ...
// Round corners if applicable
// ...
// Show separator if applicable
if numberOfRowsInSection > 1 && indexPath.row < numberOfRowsInSection - 1 {
let bottomBorder = CALayer()
bottomBorder.frame = CGRect(x: self.separatorInset.left, y: cell.bounds.maxY - 0.3, width: cell.contentView.frame.size.width, height: 0.3)
bottomBorder.backgroundColor = self.separatorColor?.cgColor
cell.contentView.layer.addSublayer(bottomBorder)
}
}
Now we’re all set to implement this in our own table view! We can do so by calling useRoundedSectionCorners()
on our table view in our view controller’s viewDidLoad()
, and by calling display(withRoundedSectionCorners:at:)
inside our table view delegate’s tableView(_:willDisplay:forRowAt:)
method, like so:
override func viewDidLoad() {
super.viewDidLoad()
// Add padding to the left and right sides
self.tableView.snp.remakeConstraints { make in
make.top.bottom.equalToSuperview()
make.left.right.equalToSuperview().inset(16)
}
self.view.backgroundColor = self.tableView.backgroundColor
// Enable rounded section corners
self.tableView.useRoundedSectionCorners()
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
tableView.display(withRoundedSectionCorners: cell, at: indexPath)
}
…and here’s our final result!
Thanks for reading! If you want to try this out in your app, download the full code here.