Adding Space Between the Cells of a UITableView

Adding Space Between the Cells of a UITableView

Exordium

In this post, I will be showing you how to create a UITableView that has spacing between the cells. I came across this design as I was recently working on a client's application. I wanted to get away from the basic table view look and use something tailored more toward the UI/UX of the client's app. For those who work as freelance software developers - versatility in design is essential because not every client wants the same thing.

To accomplish a clean table view design with spacing between the cells; you must work with sections rather than rows. Let's get into how to create a custom UITableView with spacing and clean design. This post assumes that you have previous experience with Swift, Xcode, and UITableViews.

You can find the source code to this post on GitHub. Be sure to start with the ‘start’ branch of the repo.

What Is Already Set Up

If you open the project and run it on a device or simulator, you will see the current UI layout. I have set up a basic restaurant review application, all done programmatically, and we’ll be working with the fictional burger shack of Rusty Nail. The first view controller you see is the RestaurantReviewsViewController class. There are two classes in the 'View' group; one for a custom UITableViewCell and one for a custom UIView. In the current state of the app, you can see the UIView in action, as this is the orange circle that holds the restaurant logo.

Back to the RestaurantReviewsViewController. First, I've stored a custom struct called Review that holds three properties; the writer of the review, the restaurant receiving the review, and the review's text. There is also a method named setupView that sets up the UI elements of the view, and a method named applyAutoConstraints which activates the constraints for said UI elements.

Furthermore, in the viewDidLoad method, the view's background color is set, and the setupView method gets called. Lastly, you will see a section marked Extensions and a method that makes a UIView circled.

Now that we've gone through the current code of the main view controller let's build that table view. If you want to take a look at the other code provided in the project, take your time and get to know how it all works together. Briefly, the RestaurantReviewCell is a custom table view cell that contains two labels; one for the name of the person reviewing the restaurant and one for the review.

Configuring the Table View

To start, initialize the data array with some reviews. In this case, I've used characters from the show Bob's Burgers. The data is initialized in the setupView method.

data = [
       Review(writer: "Calvin", receiver: "Rusty Nail Burgers", reviewText: "Okay burgers. Okay tenant. Would like to see them pay rent on time."),
       Review(writer: "Felix", receiver: "Rusty Nail Burgers", reviewText: "Though my brother isn't a fan of Bob's cooking, I can't find a better burger on the wharf."),
       Review(writer: "Teddy", receiver: "Rusty Nail Burgers", reviewText: "I'm here everyday. Couldn't ask for a better hangout spot."),
       Review(writer: "Mickey", receiver: "Rusty Nail Burgers", reviewText: "Hey Bob! Thanks for feeding me during that heist. I will definitely be back to visit."),
       Review(writer: "Marshmellow", receiver: "Rusty Nail Burgers", reviewText: "Hey Bob."),
       Review(writer: "Rudy", receiver: "Rusty Nail Burgers", reviewText: "My dad drops me off here every once in a while. I like to sit in the back corner and enjoy my food."),
]

Next, set the delegate and dataSource of the reviewTableView. I will conform to UITableViewDelegate and UITableViewDataSource protocols in the section marked Extensions - though you can do this directly off the RestaurantReviewsViewController class. Add the following to the setupView method after reviewTableView is initialized:

reviewTableView.delegate = self
reviewTableView.dataSource = self

The setupview method should now look like this:

func setupView() {

       data = [
            Review(writer: "Calvin", receiver: "Rusty Nail Burgers", reviewText: "Okay burgers. Okay tenant. Would like to see them pay rent on time."),
            Review(writer: "Felix", receiver: "Rusty Nail Burgers", reviewText: "Though my brother isn't a fan of Bob's cooking, I can't find a better burger on the wharf."),
            Review(writer: "Teddy", receiver: "Rusty Nail Burgers", reviewText: "I'm here everyday. Couldn't ask for a better hangout spot."),
            Review(writer: "Mickey", receiver: "Rusty Nail Burgers", reviewText: "Hey Bob! Thanks for feeding me during that heist. I will definitely be back to visit."),
            Review(writer: "Marshmellow", receiver: "Rusty Nail Burgers", reviewText: "Hey Bob."),
            Review(writer: "Rudy", receiver: "Rusty Nail Burgers", reviewText: "My dad drops me off here every once in a while. I like to sit in the back corner and enjoy my food."),
        ]

        restaurantImageViewBackground = RestaurantImageViewBackground()
        view.addSubview(restaurantImageViewBackground)

        restaurantImageView = UIImageView(image: UIImage(named: "cheese-burger"))
        restaurantImageView.translatesAutoresizingMaskIntoConstraints = false
        restaurantImageViewBackground.addSubview(restaurantImageView)

        restaurantNameLabel = UILabel()
        restaurantNameLabel.translatesAutoresizingMaskIntoConstraints = false
        restaurantNameLabel.text = "Rusty Nail Burgers"
        restaurantNameLabel.font = UIFont.boldSystemFont(ofSize: 28)
        restaurantNameLabel.textColor = .black
        view.addSubview(restaurantNameLabel)

        starStackView = UIStackView()
        starStackView.translatesAutoresizingMaskIntoConstraints = false
        starStackView.alignment = .center
        starStackView.axis = .horizontal
        starStackView.spacing = 5

        for _ in 0...5 {
            let starView = UIImageView(image: UIImage(systemName: "star.fill"))
            starView.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
            starView.tintColor = .black
            starStackView.addArrangedSubview(starView)
        }

        view.addSubview(starStackView)

        reviewTableView = UITableView()
        reviewTableView.translatesAutoresizingMaskIntoConstraints = false
        reviewTableView.delegate = self
        reviewTableView.dataSource = self
        view.addSubview(reviewTableView)

        applyAutoConstraints()
    }

The UITableViewDataSource is set up like any other table view. We return one row in each section and configure the cells based on our custom RestaurantReviewCell class. Before setting up the cell, register the UITableViewCell class used with the table view. Add the following to UITableView setup:

reviewTableView.register(RestaurantReviewCell.self, forCellReuseIdentifier: "reviewCell")

To set up the cell, add the following to the cellForRowAt method:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // 1
    let cell = tableView.dequeueReusableCell(withIdentifier: "reviewCell", for: indexPath) as! RestaurantReviewCell

    // 2
    if let writer = data[indexPath.section].writer {
        cell.reviewerLabel.text = "\(writer) said:"
    }  
    if let reviewText = data[indexPath.section].reviewText {
        cell.reviewLabel.text = "\(reviewText)"
    }

    // 3
    return cell
 }

Here's what that does:

  1. Dequeue the cell, using the reviewCell identifier and the RestaurantReviewCell class.
  2. Pull the data, based on the section being displayed. First, unwrap the value of the writer property of our data source (remember that this is of the type Review). Then, unwrap the value of reviewText.
  3. Return the cell.

UI Work with the Table View

Next, in the UITableViewDelegate, set the number of sections of the table view to the count of the array data to create the number of sections needed to display the correct amount of data.

func numberOfSections(in tableView: UITableView) -> Int {
    return data.count
}

Set the height of the rows to 140 (this can be changed according to your use case).

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 140
}

Now, to add the spacing to the cells, we must create a view for each section's header. Call the viewForHeaderInSection method, and add the following:

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    // 1
    let headerView = UIView()
    // 2
    headerView.backgroundColor = view.backgroundColor
    // 3
    return headerView
}

Here is the breakdown of what was just added:

  1. Create the header view as a UIView.
  2. Set the background color of the header to the view controller's view.backgroundColor property. (This creates a design effect that makes the header look transparent).
  3. Return the headerView to set the header for each section of the table view. This produces a basic spacing between the cells.

The height of the header can be set as followed:

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 20
}

The UITableViewDelegate and UITableViewDataSource should look like this:

extension RestaurantReviewsViewController: UITableViewDelegate {
    func numberOfSections(in tableView: UITableView) -> Int {
        return data.count
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 140
    }

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let headerView = UIView()
        headerView.backgroundColor = view.backgroundColor
        return headerView
    }

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 20
    }
}

extension RestaurantReviewsViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "reviewCell", for: indexPath) as! RestaurantReviewCell

        if let writer = data[indexPath.section].writer {
            cell.reviewerLabel.text = "(writer) said:"
        }

        if let reviewText = data[indexPath.section].reviewText {
            cell.reviewLabel.text = "(reviewText)"
        }

        return cell
    }
}

Finishing Up

Earlier I mentioned a 'clean' design. Right now, each cell's design looks like a basic UITableViewCell; a white rectangle. To add to the 'cleanliness' of the design, change the leadingAnchor and trailingAnchor to push the table view sides 20 points away from each side of the view:

reviewTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
reviewTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),

The cells are still basic white rectangles, even though the RestaurantReviewCell has its layer.cornerRadius set to 20. This is because the table view's background color is white. To fix this, we must remove the background color of the table view, which is currently white. Remove the background of the table view by setting the backgroundColor property of the table view to .clear.

reviewTableView.backgroundColor = .clear

Finally, since this is a basic list, tapping the cells is useless and doesn't add to the user experience. To disable tapping on the table view, set the allowsSelection property to false.

reviewTableView.allowsSelection = false

Closing Notes

Thanks for stopping by Rusty Nail Software and reading one of our blog posts! If you'd like to get in touch with me, you can find me on Twitter and LinkedIn. If you and your team are looking for a dependable iOS Developer, let's talk about your project. Feel free to get in touch on social media, or via [email].