Making UITableView cell highlight with UIScrollView and UIStackView

Masamichi Ueta
3 min readDec 6, 2019

--

Sometimes we create UITableView layout by UIScrollView and UIStackView.

Although UITableView supports cell highlight by default, UIScrollView and UIStackView layout do not support it.

This article is how to copy the behavior of the cell highlight of UITableView to UIScrollView and UIStackView layout.

The repository is here:

View Structure

Storyboard

This is the storyboard of StackViewCellHighlight.

UIStackView is placed in UIScrollView, and views for the cell are placed in UIStackView.

In each cell, a custom view SelectableCellView is placed. This SelectableCellView implements highlight logic when selected.

UIScrollView is a custom class called UIButtonCancelableScrollView that can cancel UIButton events. By using this scroll view, you can scroll smoothly if you scroll while selecting cells.

SelectableCellView and SelectableButton

SelectableCellView has SelectableButton inside.

SelectableButton defines setSelected (_ selected: Bool, animated: Bool) function and calls it when the selected state of the cell is changed. It manages isSelected state and processes background color. SelectableButton also overrides isHighlight and handles background-color along with isSelected.

class SelectableButton: UIButton {

override var isHighlighted: Bool {
get {
return super.isHighlighted
}
set {
backgroundColor = (isSelected || newValue) ? UIColor.opaqueSeparator : UIColor.white
super.isHighlighted = newValue
}
}

func setSelected(_ selected: Bool, animated: Bool) {
func update() {
isSelected = selected
backgroundColor = selected ? UIColor.opaqueSeparator : UIColor.white
}
if animated {
UIView.animate(withDuration: 0.35, animations: update)
} else {
update()
}
}

}

In SelectableCellView, handlers are set for touchDown, touchCancel, and touchUpInside of SelectableButton. They handle the selection state for each event.

class SelectableCellView: UIView {

private lazy var button: SelectableButton = {
let button = SelectableButton()
return button
}()

required init?(coder: NSCoder) {
super.init(coder: coder)
addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true
button.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0).isActive = true
button.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0).isActive = true
button.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true

button.addTarget(self, action: #selector(touchDown(button:)), for: .touchDown)
button.addTarget(self, action: #selector(cancel(button:)), for: .touchCancel)
button.addTarget(self, action: #selector(handleTap(button:)), for: .touchUpInside)
}

var didTap: (() -> Void)?

@objc private func handleTap(button: SelectableButton) {
setSelected(true, animated: false)
didTap?()
}

@objc private func touchDown(button: SelectableButton) {
setSelected(true, animated: false)
}

@objc private func cancel(button: SelectableButton) {
setSelected(false, animated: false)
}

func setSelected(_ selected: Bool, animated: Bool) {
button.setSelected(selected, animated: animated)
}
}

Now when the view placed in UIStackView is tapped, the background will be highlighted.

NavigationController swipe back support

UITableViewController has a setting called clearsSelectionOnViewWillAppear. When you select a cell and make a push transition, it turns off the cell highlight when you return to the screen.

To achieve this, you need to keep the selected cell and handle the selection state appropriately in the ViewController’s viewWillAppear.

class ViewController: UIViewController {

....

var selectedCell: SelectableCellView?
var clearsSelectionOnViewWillAppear: Bool = true

....

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let selectedCell = selectedCell, clearsSelectionOnViewWillAppear {
transitionCoordinator?.animate(alongsideTransition: { _ in
selectedCell.setSelected(false, animated: animated)
}, completion: { context in
if context.isCancelled {
selectedCell.setSelected(true, animated: false)
} else {
self.selectedCell = nil
}
})
}
}
}

Now, the highlight of UITableView can be realized with UIScrollView and UIStackView.

Here is a sample code:

If you like this post, please follow me @masamichiueta

--

--