Implementing sorting by product price and brand in iOS app with Swift
I don’t know how our clients managed to get by without sorting in our iOS app, but it’s finally coming.
I want to allow users to sort products in UICollectionView by price, unit price, popularity (how often products are bought), brand name (group brand products together).
Implement SortViewController
Let’s begin by creating new view controller in storyboard with navigation bar to show title and table view underneath.
SortViewController in storyboard
Table view will contain only 4 cells, and I want view controller to pop from the bottom so user can easily reach table view cells. That’s why I set custom height.
Setup constraints to stretch navigation bar horizontally and fill up rest of the view with table view. Don’t forget to set Storyboard ID (as I just did) and set view controller Custom Class to SortViewController which we’ll create later.
Finally create table view prototype cell with SortCell reuse identifier.
I will use enum to strongly type sort option currently in effect. Proceed by creating SortBy enum with 4 cases:
- Sort by price
- Sort by unit price
- Sort by popularity
- Sort by brand
I added failable initializer to enum to convert cell index into SortBy enum case. Then I added description property to get sort option title for table view cell. Most of the application is built with Objective C, I have to mark enum with @objc it accessible from Objective C.
Let’s create SortViewController.swift and implement UITableViewDataSource:
To show which sort option is in effect right now I use table view cell checkmark accessory.
Next, we need to tell anyone interested when user changed sort order, I chose to use protocol:
Add delegate property to SortViewController:
and implement UITableViewDelegate by calling delegate with selected sort option:
Integrate SortViewController
Time to hook up SortViewController with rest of the app. I want to enable product sorting in individual aisles. Each aisle is displayed in UICollectionView as a grid of products, with header on top. I have custom class to represent aisle header GRProductCollectionHeaderView with a button which I’ll use to show SortViewController.
I want to keep GRShelfViewController focused, and instead of implementing additional protocols inside I will create Swift extension for sorting:
I found strange variable delay between call to dismissViewControllerAnimated:completion
and actual dimissal. Delayed dismissal until next run loop and all worked fine.
I find it limiting to always reach for close button in top left or right corner of the view to dismiss view controller. Thankfully I found ZFModalTransitionAnimator which implements custom transition animation and allows user to drag view to dismiss. Create ZFModalTransitionAnimator
inside enableInteractiveTransitionFor
and pass it to SortViewController, so it can assigned it’s UITableView so ZFModalTransitionAnimator will dismiss view controller on swipe down.
Sort Options Popup
Sorting by price
I think sorting belongs to aisle so I will implement it in my GRShelf
class:
I had to use raw enum value and NSInteger in Objective C code, because of circular dependency. The reason is I included GRShelf.h
in Swift bridging header and have to include Swift header into GRShelf.h
in order to use SortBy
enum.
Animate UICollectionView cell reordering
After sorting products in the model I want to animate UICollectionView cell reordering. I enumerate all products in ordered array, find which row index given product had under old order and tell UICollectionView to moveItemAtIndexPath:toIndexPath
from old index path to new index path.
Sorting kinda works now, but since I use variable height for each row of 3 cells, I have to recalculate maximum height for each row of cells. I do calculations in GRShelfViewController.calculateCellSizesForProductRows
method, call it after moving cells but before finishing batch updates:
Sort by product unit price
I want to give people ability to compare similar product prices even if they have different weights, like candy 250g for $5 and 320g for $7, first one is cheaper .
So unit is the minimum indivisible quantity of product which. I add new GRProduct.fractionalPrice
:
And extend sortProducts:by:
method with new case:
Remember and restore aisle sort order
I want to keep sorting functionality close together until it will get in the way. Let’s add computed property to GRShelfViewController extension to store sort order:
Even if there is no stored ordering for the aisle, sortOrder
will return SortBy.Popularity
, which exactly how server orders by default. I will skip sorting by Popularity during initial load of UICollectionView:
Let’s see what we’ve got with production DB:
Sorting
Thanks for reading! As you may have noticed that’s just stream of consciousness as I develop new features for our app.
I feel like there are a lot of tutorials, which cover only abstract features of the language or Cocoa frameworks. I wanted to show how all of this applicable to real world development where you switch roles from backend to front end, fix bugs and debug weird behaviour.