When users added or deleted filters in the first iteration of my
Filter Chaining application, the updated array of user defined filters was passed into the
FiltersCollectionView instance where is simply invoked
reloadData() on its
UICollectionView. This worked fine, but there was no transition and the new filter suddenly jumped into view.
It would be better to animate the insert or delete and indicate to the user, through movement, how their collection of filters had changed. How to do this wasn’t immediately obvious and this post describes the technique I’ve used.
Inside the view controller, both the deleteSelectedFilter() and addNewFilter() methods update its userDefinedFilters array:
// deleteSelectedFilter
userDefinedFilters = userDefinedFilters.filter({!($0 == previousFilter)})
// addNewFilter
userDefinedFilters.insert(newFilter, atIndex: userDefinedFilters.count - 1)
and this, in turn, sets the userDefinedFilters property on the FiltersCollectionView via a didSet observer. When that property changes,FiltersCollectionView’s own didSet observer on userDefinedFilters looks at the new version of the array to see whether an item has been added (i.e. the length of oldValue is less that the new value) or deleted (i.e. the length of oldValue is greater than the new value).
Selecting, adding and deleting items in a UICollectionView uses NSIndexPaths rather than, for example, integer indexes or items. Getting theNSIndexPath for a specific item needs an intermediate step. My application always inserts new filters at the penultimate position in the collection view, so I need to find the NSIndexPath for the last but one cell and invoke insertItemsAtIndexPath(). To do this, I use on of the extensions that UICollectionView provides to NSIndexPath - a new init() method:
init(forItem item: Int, inSection section: Int) -> NSIndexPath
…and the implementation to get the required index path is and insert an item is:
let insertIndexPath = NSIndexPath(forItem: oldValue.count - 1, inSection: 0)
uiCollectionView.insertItemsAtIndexPaths([insertIndexPath])
When a user deletes a filter, the application assumes they are deleting the selected filter (a fuller implementation would compare oldValueand userDefinedFilters to figure out what’s been deleted). So, in this case, I need to invoke deleteItemsAtSelectedPaths() for the index paths of the selected items:
let deleteIndexPath = uiCollectionView.indexPathsForSelectedItems()[0] as NSIndexPath
uiCollectionView.deleteItemsAtIndexPaths([deleteIndexPath])
As well as adding or removing filters, both of these set FilterCollectonView’s selectedFilter property. When a filter is added, the new filter is selected and when a filter is removed, the first filter is selected.
Inside its didSet observer, I scroll the selected cell into view so that if the user selects a cell which is partially off-screen it becomes fully visible. In this case, I want to find the index of the selected cell inside userDefinedFilters, then create an NSIndexPath for that index and finally selectItemAtIndexPath() on the new NSIndexPath.
To that that, the didSet observer on selectedFilter looks like this:
var selectedFilter : UserDefinedFilter?
{
didSet
{
var selectedIndex : Int = -1
for (i: Int, filter: UserDefinedFilter) in enumerate(userDefinedFilters)
{
if filter == selectedFilter!
{
selectedIndex = i
}
}
if selectedIndex != -1
{
let selectedIndexPath = NSIndexPath(forItem: selectedIndex, inSection: 0)
uiCollectionView.selectItemAtIndexPath(selectedIndexPath, animated: true, scrollPosition: UICollectionViewScrollPosition.CenteredHorizontally)
}
sendActionsForControlEvents(.ValueChanged)
refresh()
}
}
There we have it: rather than cells jarringly appearing and disappearing, a little work with NSIndexPath and properly using UICollectionView’s own insert and delete support gives a smooth transition.
Comments