VButko

How to Improve UITableView and UICollectionView Cell Deselection with Swift Code Examples

Download materials

Every iOS developer recognizes the importance of fine-tuning and polishing the user interface to provide a seamless, intuitive, and engaging user experience. One aspect that sometimes receives less attention, but can make a significant difference in UI fluency, is the proper handling of cell deselection in UITableView and UICollectionView when transitioning between views. When executed well, this not only lends a professional touch to your application but also enhances its functionality.

In this article, we'll explore how to properly deselect cells during transitions, replicating the native SwiftUI NavigationLink deselection behavior in UIKit. Additionally, I'll provide a handy UIViewController extension that can be readily incorporated into your apps. This will assist in making your code cleaner and more efficient. If you aim to give your apps a polished, professional feel, then keep reading.

Handling Cell Deselection in UICollectionView

Many apps display lists – be it tasks, notes, settings, or something else. The built-in behavior of a UITableView or UICollectionView is to highlight a cell when it's tapped. We typically navigate from this tap to a detail screen, and it's often desired to deselect the cell when the user returns to the list screen, indicating what item was previously selected.

A straightforward implementation could look like this:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    if let selectedIndexPath = collectionView.indexPathsForSelectedItems?.first {
        collectionView.deselectItem(at: selectedIndexPath, animated: true)
    }
}

While this approach works, it's not ideal. The deselection animation doesn't sync with the screen transition and doesn't cover a scenario where the user starts a swipe-back gesture but decides not to go back. This is where the transitionCoordinator comes into play - a feature used by default in SwiftUI and in Apple's apps.

Improving Deselection with Transition Coordinator

UIViewController has a transitionCoordinator: UIViewControllerTransitionCoordinator? property, which is not nil during an active transition. The following code deselects the cell in sync with the transition and reselects it if the transition gets cancelled:

transitionCoordinator?.animate(alongsideTransition: { context in
    collectionView.deselectItem(at: indexPath, animated: true)}) { context in
        if context.isCancelled {
            collectionView.selectItem(at: indexPath, animated: false, scrollPosition: [])
        }
    }

To simplify its use, we can encapsulate this into an extension:

extension UIViewController {
    func deselectItem(withTransition: Bool = true, collectionView: UICollectionView?) {
        guard let collectionView = collectionView else { return }
        
        if let indexPath = collectionView.indexPathsForSelectedItems?.first {
            if withTransition, let coordinator = transitionCoordinator {
                coordinator.animate(alongsideTransition: { context in
                    collectionView.deselectItem(at: indexPath, animated: true)}) { context in
                        if context.isCancelled {
                            collectionView.selectItem(at: indexPath, animated: false, scrollPosition: [])
                        }
                    }
            } else {
                collectionView.deselectItem(at: indexPath, animated: true)
            }
        }
    }
}

With this in place, we can call this method in viewWillAppear() or other relevant places:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    deselectItem(collectionView: collectionView)
}

This new approach provides a more synchronized and smooth cell deselection that aligns with best practices, delivering a polished, professional user experience.

Conclusion

In the world of iOS development, it's often the fine details that set apart good apps from great ones. Properly handling cell deselection in UITableView and UICollectionView is one of those details. This small feature contributes to the overall user experience in a meaningful way.

In this article, we've explored how to mimic SwiftUI's native NavigationLink deselection behaviour in UIKit, enhancing the transition between different views. The key to achieving this is the transitionCoordinator, which synchronizes the deselection animation with screen transitions and handles the scenario where users change their mind mid-swipe. By implementing the UIViewController extension provided, you can incorporate this technique into your apps.

I hope you enjoyed this article. If you have any questions, suggestions, or feedback, please let me know on Twitter.

You can download a sample project with the implementation via the link at the top of the page.

Thanks for reading!