Henrik Panhans

iOS Developer & CS Student

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 UINavigationControllerto my empty ViewController and connected it as root controller.

Then I added a UIScrollViewto the ViewController and added constraints to the superview and set them all to 0. After that I dragged a UITableViewinto 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 UIScrollViewas 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.swiftand 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: Ugly separators

So to fix that I set the tableviews separator color to UIColor.clearand instead added a new CALayerto 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! Finished Tableview

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 :-)