قالب وردپرس درنا توس
Home / Apple / UICollectionView Tutorial: Preamble of APIs | raywenderlich.com

UICollectionView Tutorial: Preamble of APIs | raywenderlich.com



As a developer you should always strive to provide a good user experience. One way to do this in programs that lists lists is to make sure the scroll is silky. In iOS 10, Apple introduced UICollectionView prefetching APIs and corresponding UITableView prefetching APIs, which allow you to retrieve data before collections and table views need it.

When you come across an app with chopped scrolling, this usually results in a long running process that blocks the root breakthrough from updating. You will keep the main group free to answer things like touch events. A user can forgive you if you take some time to retrieve and view data, but they will not be so forgiving if your app is not responding to its movements. Moving heavy work to a background thread is a great first step in building a responsive app.

In this tutorial, you start working with EmojiRater an app that shows a collection of emojis. Unfortunately, its rolling performance leaves much to be desired. You use the prefetch APIs to determine which cells your app likely will show soon and trigger related data retrieval in the background.

Getting Started

Use the Download Material button at the top or bottom of this tutorial to download the Startup. Build and run the app. You should see something like you try to scroll:

 starts

Painful, right? Does it remind you of the tablet experience? You know it there … not mind. The good news is that you can fix this.

A little about the app. The app shows a collection of emojis that you can downvote or upvote. If you want to use, click on one of the cells and then press until you feel a little haptic feedback. The classification selection must be displayed. Select one and see the result in the updated collection view:

 app_2

Note : If you have trouble getting 3D Touch to work in the simulator, you first need a Mac or MacBook with a trackpad with "Force Touch" function. You can then go to System Preferences ▸ Track Cushion and enable Power Clicks and Haptic Feedback . If you do not have access to such a device or iPhone with 3D Touch, you will still be able to get the essentials in this tutorial.

Take a look at the project in Xcode. These are the main files:

  • EmojiRating.swift : Model representing an emoji.
  • DataStore.swift : Loading an emoji.
  • EmojiViewCell.swift : Collection display displaying
  • RatingOverlayView.swift : Show that allows the user to evaluate an emoji.
  • EmojiViewController.swift : Displays the emojis in a collection view.

You add functionality to DataStore and EmojiViewController to improve the scroll performance.

Understand Rough Rolling

You can achieve smooth scrolling by making sure your app meets the 60-second (FPS) restriction display. This means that your app needs to be able to update the user interface 60 times a second, so each frame has approx. 16 ms to render content. The system fills frames that take too long to display content.

This results in a choppy scroll experience as the app skips the frame and moves to the next frame. A possible cause of a lost frame is a long-term operation that blocks the headline.

 boring

Apple has provided some practical tools to help you. First, you can share your long-term operations and move them to a background thread. This allows you to handle any touching events, as they happen, on the main thread. Once the background operation is completed, you can make any necessary user interface updates, based on the operation, on the main thread.

The following shows the fallen frame scenario:

 no-concurrency

After moving work to the background, it looks like this:

 Concurrence

You now have twofold threads running to improve the app's performance.

Would not it be even better if you could get data before you had to show it? That's where UITableView and UICollectionView prefetching APIs come in. You use the collective view APIs in this guide.

Loading data Asynkront

Apple provides a number of ways to add sync to your app. You can use the Grand Central Dispatch (GCD) as a lightweight mechanism to perform tasks at the same time. Or, you can use Operation, built on top of GCD.

Operation adds more overhead, but makes it easy to reuse and cancel operations. You use Operation in this tutorial so that you can cancel an operation that previously started loading an emoji that you no longer need.

It's time to start exploring the best way to exploit the simultaneousity of EmojiRater .

Open EmojiViewController.swift and find the data collection method collectionView (_: cellForItemAt :) . Look at the following code:

  if la emojiRating = dataStore.loadEmojiRating (on: indexPath.item) {
cell.updateAppearanceFor (emojiRating, animated: true)
}

This loads emoji from the data store before you show it. Let's figure out how it's implemented.

Open DataStore.swift and take a look at the loading method:

  public func loadEmojiRating (by index: Int) -> EmojiRating? {
if (0 .. <emojiRatings.count) .contains (index) {
la randomDelayTime = Int.random (in: 500 .. <2000)
usleep (useconds_t (randomDelayTime * 1000))
return emojiRatings [index]
}
return .none
}

This code returns a valid emoji after a random delay that can range from 500ms to 2000ms. The delay is an artificial simulation of a network request under varying conditions.

Sins revealed! Emoji retrieval takes place on the main wire and breaks with the 16ms threshold, triggering dropped frames. You are about to fix this.

 leave me-on-it

Add the following code to the end of DataStore.swift :

  Class DataLoadOperation: Operation {
// 1
was emojiRating: EmojiRating?
was loadingCompleteHandler: ((EmojiRating) -> Feid)?

private la _emojiRating: EmojiRating

// 2
init (_ emojiRating: EmojiRating) {
_emojiRating = emojiRating
}

// 3
override func main () {) {
// TBD: Work it!
}
}

Operation is an abstract class that you must subclass to implement the work you want to move from the main thread.

Here's what happens in the code step by step:

  1. Create a reference to the emoji and finisher that you will use in this operation.
  2. Create a particular initialiser so that you can pass in an emoji.
  3. Override the main method () to perform actual work for this operation.

Now add the following code to main () :

  // 1
if erCancelled {return}

// 2
la randomDelayTime = Int.random (in: 500 .. <2000)
usleep (useconds_t (randomDelayTime * 1000))

// 3
if erCancelled {return}

// 4
emojiRating = _emojiRating

// 5
if la loadCompleteHandler = loadingCompleteHandler {
DispatchQueue.main.async {
loadingCompleteHandler (self._emojiRating)
}
}

Passes the code step by step:

  1. Check for cancellation before starting. Operations should check regularly if they have been canceled before attempting long or intensive work.
  2. Simulate the long-lasting emoji retrieval. This code should be known.
  3. Check if the operation has been canceled.
  4. Assign emoji to indicate that the download has completed.
  5. Call the finisher on the main thread and pass emoji. This should trigger a UI update to display emoji.

Replace loadEmojiRating (on :) with the following:

  public func loadEmojiRating (by index: Int) -> DataLoadOperation? {
if (0 .. <emojiRatings.count) .contains (index) {
return DataLoadOperation (emojiRatings [index])
}
return .none
}

There are two changes from the original code:

  1. You create a DataLoadOperation () to retrieve emoji in the background.
  2. This method now returns a DataLoadOperation optional instead of an EmojiRating optional.

You must now take care of the method change and make use of that brand new operation.

” width=”192″ height=”236″ class=”aligncenter size-full wp-image-204911″/> oh-yeah ” width=”192″ height=”236″ class=”aligncenter size-full wp-image-204911″/>

Open EmojiViewController.swift and in collectionView (_: cellForItemAt :) delete the following code:

  if la emojiRating = dataStore.loadEmojiRating (by: indexPath.item) {
cell.updateAppearanceFor (emojiRating, animated: true)
}

You will no longer turn off data collection from this data source method. Instead, you will do this in the delegate method called when your app will show a compilation cell. Do not be in front of yourself yet …

Add the following properties near the top of the class:

  la loadingQueue = OperationQueue ()
was loadingOperations: [IndexPath: DataLoadOperation] = [:]

The first property keeps the queue of operations. loadingOperations is a matrix that tracks a data load operation, which unites each loading operation with the associated cell via its index path.

Add the following code to the end of the file:

  // MARK: - UICollectionViewDelegate
extension EmojiViewController {
override func collectionView (_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt IndexPath: IndexPath) {
guard la cells = cell som? EmojiViewCell Other {return}

// 1
let updateCellClosure: (EmojiRating?) -> Void = {[weak self] emojiRating in
watch leave myself = even else {
return
}
cell.updateAppearanceFor (emojiRating, animated: true)
self.loadingOperations.removeValue (forKey: indexPath)
}

// 2
if let dataLoader = loadingOperations [indexPath] {
// 3
if let emojiRating = dataLoader.emojiRating {
cell.updateAppearanceFor (emojiRating, animated: false)
loadingOperations.removeValue (forKey: indexPath)
} other {
// 4
dataLoader.loadingCompleteHandler = updateCellClosure
}
} other {
// 5
if let dataLoader = dataStore.loadEmojiRating (at: indexPath.item) {
// 6
dataLoader.loadingCompleteHandler = updateCellClosure
// 7
loadingQueue.addOperation (DataLoader)
// 8
loadingOperations [indexPath] = dataLoader
}
}
}
}

This creates an extension for UICollectionViewDelegate and implements the delegation method collectionView (_: willDisplay: forItemAt :) . Gives the procedure step by step:

  1. Create a shutdown to handle how the cell is updated when the data is loaded.
  2. Check if there is a data upload transaction for the cell.
  3. Check if data loading operation has completed. If so, update the cell's user interface and remove the operation from the tracking event.
  4. Assign closure to data update handler if emoji is not retrieved.
  5. If there is no data upload, create a new one for the current emoji.
  6. Add the closing to the data completion handler.
  7. Add operation to your operating queue.
  8. Add the data reader to the operation tracking event.

]

Add the following method to UICollectionViewDelegate extension:

  override func collectionView (_ collectionView: UICollectionView,
didEndDisplaying cell: UICollectionViewCell,
forItemAt IndexPath: IndexPath) {
if let dataLoader = loadingOperations [indexPath] {
dataLoader.cancel ()
loadingOperations.removeValue (forKey: indexPath)
}
}

This code checks an existing data-loading operation that is bound to the cell. If one exists, it interrupts the download and removes the operation from the array that follows operations.

Build and run the app. Browse the emoji and notice the improvement in the app's performance.

If you could optimistically retrieve the data while pending a compilation screen, it would be even better. You want to use the prefetch APIs to do this, and give EmojiRater an additional boost.

Enable UICollectionView Prefetching

Protocol UICollectionViewDataSourcePrefetching Notifies you that data for a collection view may be needed soon. You can use this information to start retrieving data so that the data is already available when the cell is visible. This works with the concurrent work you have already done – the key difference is when your work is kicked off.

The chart below shows how this plays. The user scrolls upward on a collection view. The yellow cell will appear soon – suppose this happens in Frame 3 and you are currently in Frame 1 .

 prefetch-steps

The decision of the prefetch protocol informs the app of the next cells that may become visible. Without the prefetch trigger, data collection for the yellow cell begins in Frame 3 and the cell's data will be visible sometime later. Due to the prefetch, cell data will be clear when the cell is visible. Open EmojiViewController.swift and add the following code to the end of the file:

  // MARK: - UICollectionViewDataSourcePrefetching
extension EmojiViewController: UICollectionViewDataSourcePrefetching {
func collectionView (_ collectionView: UICollectionView,
prefetchItemsAt IndexPaths: [IndexPath]) {
print ("Prefetch:  (indexPaths)")
}
}

EmojiViewController now adopts UICollectionViewDataSourcePrefetching and implements the required delegation method. The implementation only prints the index paths that may be visible soon.

In viewDidLoad () add the following following the call to super.viewDidLoad () :

  collectionView? .prefetchDataSource = itself

This sets EmojiViewController as the collection view's prefetching data source.

Build and run the app and check the Xcode console before rolling. You should see something like this:

  Prefetch: [[0, 10][0, 11][0, 12][0, 13][0, 14][0, 15]]

These correspond to cells that are not yet visible. Now, browse more and check the console log as you do. You should see log messages based on index paths that are not visible yet. Try to scroll both up and down until you get a good sense of how all this works.

You may wonder why this delegated method only gives you index paths to work with. The idea is that you should turn off data uploading processes from this method and handle the results in collectionView (_: cellForItemAt :) or collectionView (_: willDisplay: forItemAt :) . Note that the delegate method is not called when cells are required immediately. Therefore, you should not trust that you load data into the cells in this method.

Data Retrieval

In EmojiViewController.swift modify collectionView (_: prefetchItemsAt :) by replacing print () the sentence with the following:

  for indexPath in indexPaths {
// 1
if la _ = loadingOperations [indexPath] {
Continue
}
// 2
if let dataLoader = dataStore.loadEmojiRating (at: indexPath.item) {
// 3
loadingQueue.addOperation (DataLoader)
loadingOperations [indexPath] = dataLoader
}
}

The code runs through the index paths the method receives and does the following:

  1. Checks if there is an existing loading operation for this cell. If that's it, there's nothing more to do.
  2. Creates a data upload transaction if it can not find a loading operation.
  3. Adds the operation to the queue and updates the dictionary that tracks data transfer operations. [19659048] Index paths went into collectionView (_: prefetchItemsAt :) ordered by priority based on the cell's geometric distance to the compilation view. This allows you to pick up the cells you most likely need first.

    Remember that you previously added code to collectionView (_: willDisplay: forItemAt :) to handle the results of the loading operation. Look at the highlights of the method below:

      override func collectionView (_ collectionView: UICollectionView,
    willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    // ...
    let updateCellClosure: (EmojiRating?) -> Void = {[weak self] emojiRating in
    watch leave myself = even else {
    return
    }
    cell.updateAppearanceFor (emojiRating, animated: true)
    self.loadingOperations.removeValue (forKey: indexPath)
    }
    
    if let dataLoader = loadingOperations [indexPath] {
    if let emojiRating = dataLoader.emojiRating {
    cell.updateAppearanceFor (emojiRating, animated: false)
    loadingOperations.removeValue (forKey: indexPath)
    } other {
    dataLoader.loadingCompleteHandler = updateCellClosure
    }
    } other {
    // ...
    }
    }
    

    After creating cell update shutdown, check the supplied array. If it exists for the cell to be displayed and emoji is available, the cell's user interface is updated. Note that the closure that passes to the data management operation also updates the cell user interface.

    This is how everything binds, from prefetch that triggers an operation to the cell user interface, is updated.

    Build and run the app and browse emojis. Emojis you roll to will be visible much faster than before.

     UICollectionView prefetching in action!

    Pop quiz time! Can you spot something that can be improved? No cheating by looking at the next title title. Well, if you roll very fast, the collection will start to pick up emojis that will never be seen. What is an obsessive programmer to do? Read on.

    Cancel a Prefetch

    UICollectionViewDataSourcePrefetching has an optional delegate method that lets you know that data is no longer required. This can happen because the user has started to roll very fast and intermediate cells will probably not be seen. You can use the delegate method to cancel any pending data upload transactions.

    Still in EmojiViewController.swift add the following method to UICollectionViewDataSourcePrefetching protocol implementation:

      func collectionView (_ collectionView: UICollectionView,
    CancelPrefetchingForItemsAt IndexPaths: [IndexPath]) {
    for indexPath in indexPaths {
    if let dataLoader = loadingOperations [indexPath] {
    dataLoader.cancel ()
    loadingOperations.removeValue (forKey: indexPath)
    }
    }
    }
    

    The code runs through the index paths and finds any loading operations associated with them. It then interrupts the operation and removes it from the dictionary that follows operations.

    Build and run the app. When you scroll very quickly, operations that have started may start interrupting. Visually things will not look much different.

    One thing to note is that it is due to reuse of cells; some previously visible cells may need to be retrieved. Do not panic if you see the loading indicator on the puppies – uhm, emojis.

    Where do you go from here?

    Congratulations! You have successfully turned a slow app to a verifiable speedster.

    You can download the final project using the Download Materials button at the top or bottom of this tutorial.

    This tutorial discussed community views, but there is a similar prefetch API available for table views. You can see an example of how this is used in the UITableView Infinite Scrolling tutorial.

    In an application with latent data loading, scrolling can not be rolled without concurrence. Be sure to check out the Operation and OperationQueue tutorial for a deeper dive about this topic.

    Add these to your boy's bag with tricks to make your app more responsive. These are the small things that add up and go a long way to keep the users happy. Happy rolling!

    I hope you have had this tutorial. If you have any comments or questions about the training, please join the forum discussion below!


Source link