Replicating the Wallet TableView in iOS 12.2 with Swift 5

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 code in this article was written in Swift 5

Setting up the Storyboard

First, I added a UINavigationControllerto 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 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*

Adding Cells

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

Note the .clear color. Swift knows what type is expected so we don’t have to specify it everytime we’re assigning an instance variable

TableView Behavior

Another thing that was necessary get the scrollview to behave and display the tableview 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()
}

Rounding the Cells

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 are rounded by 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

A tableview with rounded corners on each first and last cell in a second but the cell separators are ugly and cut off
Ugly separator lines

Those separator lines are ugly and glitchy. 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)
    }
}

Now that’s really close to what Apple did in the Wallet app. Now you can of course add custom cells to your table and do whatever you want with it, it works just like a normal tableview.

A tableview with rounded corners on the top and bottom of each section
Looking nice!

The code for this article is available on Github. If you liked this article or have any suggestions, let me know in the comments or send me an email. Anyways, thanks for reading 🙂

3 thoughts on “Replicating the Wallet TableView in iOS 12.2 with Swift 5”

  1. Thank you for the code. I’m already using it and works perfect. One thing… I’m trying to find a way to apply shadow as well. Any ideas on how to do that?

    1. Thank you for your feedback! You could set a shadow on each cell and then use the same approach as above to round the shadow on the top and bottom cells of each section
      In iOS 13 Apple introduced a new tableview style called “.insetGrouped” which looks the exact same as the code I used and was actually the inspiration for it if you want to check it out 🙂

  2. Very interesting code, however I did the following code for border corners

    func applyConfig(for indexPath: IndexPath, numberOfCellsInSection: Int) {
        self.layer.cornerRadius = 10
        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.layer.maskedCorners = [.layerMaxXMaxYCorner , .layerMinXMaxYCorner]
    
            // However, if it's the only one, round all four
            if numberOfCellsInSection == 1 {
                self.layer.maskedCorners = [.layerMaxXMaxYCorner , .layerMinXMaxYCorner, .layerMinXMinYCorner , .layerMaxXMinYCorner]
            }
    
        case 0:
            // If the cell is first in the section, round the top corners
            self.layer.maskedCorners = [.layerMinXMinYCorner , .layerMaxXMinYCorner]
        default:
            // If it's somewhere in the middle, round no corners
            self.layer.cornerRadius = 0
        }
    }
    

Leave a Reply

Your email address will not be published. Required fields are marked *