WWDC 2018 Session 225: A Tour Of UICollectionView

This article is not difficult, from easy to difficult, in-depth layer by layer, is a good Session. It takes about 15 minutes to read through the full text, which is about 2500 words in total.

After reading this Session, MY intuitive feeling is that this article named A Tour Of UICollectionView is A discussion on custom layout, performance optimization, data operation and animation around A CollectionView case. There are no new apis and features, but they are meaningful.

According to the idea of Session, this paper is mainly divided into three modules:

  • CollectionView overview
  • Layout (custom Layout)
  • Refresh and animate data

CollectionView is no stranger to you, in our daily development, it can be seen everywhere. If you’re not familiar with this, you can look at the previous Session:

  • WWDC 2016 – What’s New In CollectionView In iOS 10.
  • WWDC 2017-DRAG and Drop with Collection and Table View.

If we want to build an App like the one shown below, we need to involve three points: layout, refresh and animation, and our topic today will also revolve around these three points.

CollectionView overview

CollectionView has three core concepts: Layout, Data Source, and Delegate.

UICollectionViewLayout

UICollectionViewLayoutAttributes UICollectionViewLayout is responsible for the management, A UICollectionViewLayoutAttributes object manages the layout of a Item in a CollectionView related properties. Include Bounds, Center, frame, and so on. Also be aware of whether the Layout needs to be refreshed when the Bounds change, and how the Layout is animated.

UICollectionViewFlowLayout

UICollectionViewFlowLayout is a subclass of UICollectionViewLayout, is an encapsulated streaming systems provide us the layout of the class.

Horizontal flow layout (the white line represents the layout direction)

Vertical flow layout (the white line represents the layout direction)

This kind of flow layout needs to distinguish directions. With different directions, specific Line Spacing and Item Spacing represent different meanings. The specific differences can be distinguished by the two diagrams above.

Because of its applicability, flow layout is widely used in design.

UICollectionViewDataSource

Data source: As the name suggests, provides grouping information about the data, the number of items in each group, and the actual contents of each Item.

optional func numberOfSections(in collectionView: UICollectionView) -> Int

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
Copy the code

UICollectionViewDelegate

The delegate provides some fineness methods:

  • Highlighting
  • Selection

There are also some view display events:

  • willDisplayItem
  • didEndDisplayingItem

Layout – Customize Layout

System provides UICollectionViewFlowLayout though convenient and quick to use, can satisfy the basic layout needs. However, in the case of a layout like the one shown below, it is not possible to achieve the desired effect. In this case, you need to customize the FlowLayout.

Customizing FlowLayout is not complicated. There are four steps as follows:

1. Provide scrolling range
override var collectionViewContentSize: CGSize 
Copy the code
2. Provide a layout property object
func layoutAttributesForElements(in rect: CGRect)- > [UICollectionViewLayoutAttributes]? 

func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
Copy the code
3. Prepare the layout
// Call for each invalidateLayout
/ / the cache UICollectionViewLayoutAttributes
/ / collectionViewContentSize calculation
func prepare(a)
Copy the code
Handle boundary changes in a custom layout
// Whether to allow the layout to be refreshed while the CollectionView is scrolling
func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool
Copy the code

Performance optimization

Through the above methods, we can easily achieve a custom layout layout. But in practice, there is a trick that can be used to improve performance.

Usually, we get the current screen all UICollectionViewLayoutAttributes will write so

override func layoutAttributesForElements(in rect: CGRect)- > [UICollectionViewLayoutAttributes]? {
    return cachedAttributes.filter { (attributes:UICollectionViewLayoutAttributes) - >Bool in
        return rect.intersects(attributes.frame)
    }
}
Copy the code

Using the above method, we will traverse the cache all UICollectionViewLayoutAttributes cachedAttributes array. And as the user drags the screen, this method gets called a lot, which means it does a lot of calculations. When the size of a cachedAttributes array reaches a certain size, the negative impact on performance becomes apparent, with users having a negative experience of stalling.

Apple’s engineers have come up with a solution to this problem. All UICollectionViewLayoutAttributes in sequence is stored in cachedAttributes array, since this is an ordered array, so long as we through binary search, You can take any Attribures object that is displayed on the current page, and you can go back and forth and look for the qualified Attribures objects around that Attribures object, which will narrow down the scope of the search. Accordingly, the computation quantity becomes smaller, and the performance improvement is very significant.

To make it easier for you to understand, I drew a picture, which is a little ugly, but it’s enough to convey the idea. The scope of the currently displayed CollectionView is recT. Within the rect by binary search, find the first proper UICollectionViewLayoutAttributes as firstMatchIndex, also is the object Attributes.

In rect, Attributes above firstMatchIndex correspond to Attributes. Frame. maxY >= rect.miny, Attributes below firstMatchIndex also meet the condition Attributes. Frame. maxY <= rect.maxy.

The optimized code is as follows
override func layoutAttributesForElements(in rect: CGRect)- > [UICollectionViewLayoutAttributes]? {
    
    var attributesArray = [UICollectionViewLayoutAttributes] ()// Find the Index of any Attributes in the current area
    guard let firstMatchIndex = binarySearchAttributes(range: 0. cachedAttributes.endIndex, rect:rect)else { return attributesArray }
    
    // Narrow the search scope by traversing backward and forward
    for attributes in cachedAttributes[..<firstMatchIndex].reversed {
        guard attributes.frame.maxY >= rect.minY  else {break}
        attributesArray.append(attributes)
    }
    // Narrow the search scope by iterating from front to back and forward
    for attributes in cachedAttributes[firstMatchIndex...] {
        guard attributes.frame.minY <= rect.maxY  else {break}
        attributesArray.append(attributes)
    }
    
    return attributesArray
}
Copy the code

Through the way of binary search, in the process of processing the current page shows UICollectionViewLayoutAttributes can reduce the amount of data traversal, page sliding smoother in the practical experience, experience is better, this way of handling Attribures object, It is worth learning from in the development process.

Data refresh and animation

We will encounter the CollectionView to edit the scene, the edit operation is generally new, delete, refresh, insert, etc. In this Session, the presenter gave us an example.

  • The last piece of data is refreshed
  • Move the data that was in the last position to the first position
  • Delete the original third data

To make it easier to understand, let’s post the code:

/ / function
func performUpdates(a) {
    people[3].isUpdated = true
    
    let movedPerson = people[3]
    people.remove(at:3)
    people.remove(at:2)
    
    people.insert(movedPerson, at:0)
    
    // Update Collection View
    collectionView.reloadItems(at: [IndexPath(item:3, section:0)])
    collectionView.reloadItems(at: [IndexPath(item:2, section:0)])
    collectionView.moveItem(at: IndexPath(item:3, section:0), to:IndexPath(item:0, section:0))}Copy the code

This example reports an error during operation because we are deleting and moving elements at the same index location. We’re explicitly calling reloadData(). ReloadData () is an asynchronously executed function that accesses the data source method directly and rearranges it. Calling it multiple times is error-prone and does not animate.

performBatchUpdates

Add, delete, refresh, and insert the CollectionView into the updates closure in performBatchUpdates(). We don’t need to care about the update order of CollectionView items, but the update order of the data source is very important.

So let’s start with this method

func performBatchUpdates(_ updates: ((a) -> Void)? , completion: ((Bool) - >Void)? = nil)

1The updates closure will perform a series of operations to add, delete, refresh, insert and so on.2The completion closure starts executing after the Updates closure completes, and the actions in the Updates closure trigger animations that return when successfulTrueReturns when the animation is interrupted or execution failsfalseThis parameter may also be returnednil.Copy the code

This method can be used to add, delete, refresh, insert, and other elements in a batch of collectionView, and trigger the corresponding animation of the collectionView layout:

1.func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) - >NSCollectionViewLayoutAttributes?

2.func initialLayoutAttributesForAppearingDecorationElement(ofKind elementKind: NSCollectionView.DecorationElementKind, at decorationIndexPath: IndexPath) - >NSCollectionViewLayoutAttributes?

3.func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) - >NSCollectionViewLayoutAttributes?

4.func finalLayoutAttributesForDisappearingDecorationElement(ofKind elementKind: String, at decorationIndexPath: IndexPath) - >UICollectionViewLayoutAttributes?
Copy the code

The reason is that after performing the performBatchUpdates operation, the CollectionView automatically reloadData calls the data source method to rearrange. Therefore, after editing the data in the Updates closure, we must update the data source synchronously, otherwise there is a great chance that the data will be out of bounds.

There are several common types of error-prone merge updates

  • 1. Move and delete the same index
  • 2. Move and insert the same index
  • 3. Move multiple objects to the same index
  • 4. An invalid index was referenced

Apple’s engineers have good advice on how to avoid problems when performing operations. CollectionView updates () : CollectionView updates () : CollectionView updates () : CollectionView updates () : CollectionView updates (); But the order in which data sources are updated matters. What about the update order of the last Item versus the update order of the data source?

You can think of it this way:

  • In the Updates closure, you can delete an index and insert a new one, or reverse the order of the two. It doesn’t matter. You can specify the order you want.
  • However, methods involving data source updates must be operated in a certain order and according to rules.

The order and rules in which data sources perform operations

  • 1. Divide the move operation into delete and insert.
  • 2. Combine all delete operations and insert operations.
  • 3. Delete in descending order.
  • 4. Insert in ascending order.

Then we will change the error code to the following:

// New implementation
func performUpdates(a) {
    
    UIView.performWithoutAnimation {
        // Refresh the data first
        CollectionView.performBatchUpdates({
            people[3].isUpdate = true
            CollectionView.reloadItems(at: [IndexPath(item:3, section:0)])})// Then split the move into two actions: delete and insert
        CollectionView.performBatchUpdates({
            let movedPerson = people[3]
            people.remove(at: 3)
            people.remove(at: 2)
            people.insert(movedPerson, at:0)
            CollectionView.deleteItems(at: [IndexPath(item:2, section:0)])
            collectionView.moveItem(at: IndexPath(item:3, section:0), to:IndexPath(item:0, section:0))}}}Copy the code

In conclusion, engineers from Apple suggested that we use custom layout to achieve beautiful layout style, and adopt binary search to process data efficiently and improve the interface fluency and user experience.

Secondly, the operation of CollectionView suggests that we use performBatchUpdates to process, we do not need to consider the execution of the animation, because the default help us to deal with the good, we only need to pay attention to the principle and order of data source processing, to ensure the security and stability of data processing.

If you are interested in this Session, you can contact the author on Twitter. Just search for “A Tour Of CollectionView” on Twitter. The author is very enthusiastic.

Finally, THE author’s English listening is quite miserable, and some parts of my English listening are not very clear. If you find any omission or error in my information, PLEASE don’t hesitate to give me your advice.

For more WWDC 18 articles, go to xSwiftGG’s WWDC 18 catalog