You have done a lot of work on this app so far - in this step we want to make your life just a little bit easier.
We will implement a Pull-To-Refresh mechanism as you know it from most iOS apps. We will also implement a feature that will limit the amount of posts we download. Right now our approach is pretty inefficient; we download all posts in one query. Even though Parse automatically limits us to 100 posts, we are potentially downloading much more data than necessary.
After we are finished with this step, we will load posts in chunks of 5, only loading additional posts once the user reaches the end of the timeline. This behavior is well known from Facebook, Instagram, and many other apps.
To make your life easier we have delivered a lot of this functionality as part of a class called TimelineComponent. So instead of implementing all of this from scratch, you will learn how to use the TimelineComponent!
The component will be responsible for storing all of the posts displayed on the timeline. It will also be responsible for triggering requests whenever a user refreshes the timeline. This means we will restructure a fair amount of our existing code in the TimelineViewController.
First we will change our timeline query so that we can load posts in certain ranges instead of all at once. Then we will walk through the different steps of adding the TimelineComponent to Makestagram.
Making the Timeline Query More Flexible
In order to load posts in chunks, we will need to change our timeline query to accept a range parameter. That allows the caller to specify how many posts should be loaded.
Swift has a built in Range type that allows us to define a range with a start and an end index. We should change the timeline query to accept such a Range parameter.
Let's implement the change and discuss it afterwards.
Modify the timelineRequestForCurrentUser method to look as follows:
As discussed, we modify the method signature to accept a Range argument. That Range argument will define which portions of the timeline will be loaded. Ranges in Swift are defined like this: 5..10 (10 included) or 5..<10 (10 excluded).
PFQuery provides a skip property. That allows us - as suspected by the name - to define how many elements that match our query shall be skipped. This is the equivalent of the startIndex of our range, so all we need to do is a simple assignment.
We make use of an additional property of PFQuery: limit. The limit property defines how many elements we want to load. We calculate the size of the range (by subtracting the startIndex from the endIndex) and pass the result to the limit property.
This wasn't too difficult! Now we are prepared to use the TimelineComponent in our app!
Incorporating the TimelineComponent
Now we will discuss, step-by-step, how to use the TimelineComponent. Remember this step so that you can come back here in case you want to use the component in your own app!
The very first step is importing the ConvenienceKit framework into the TimelineViewController. That framework contains the TimelineComponent.
Add the following import statement to the top of TimelineViewController.swift:
Next, our TimelineViewController needs to implement the TimelineComponentTarget protocol. The TimelineComponent needs our cooperation in a couple of different ways, this protocol defines which methods and properties need to be available on classes that want to work with it.
Extend the class definition of TimelineViewController as following:
We can ignore the typealias, which leaves use with four requirements for classes that implement the TimelineComponentTarget protocol:
defaultRange: A property that defines how many posts should be loaded initially
additionalRangeSize: A property that defines how many additional posts should be loaded once the user reaches the bottom of the timeline
tableView: A reference to a UITableView - we've already set that up!
loadInRange: A method that loads a certain portion of the timeline and calls the completionBlock when it's done.
This means we need to add two properties and one method to implement this protocol.
Let's start with the two properties:
Add the following two properties to the TimelineViewController class:
We are defining that we start by showing the latest 5 posts (index 0 to 4). Whenever a user reaches the end of the timeline, we load an additional 5. You could change the behavior of your timeline by simply changing these values!
To conform to the TimelineComponentTarget protocol we need to implement one more method: loadInRange. Currently we are performing our timeline query inside of the viewDidAppear method. We first perform a query, then we update the table view.
When working with the TimelineComponent, we are no longer responsible for updating the table view and starting the queries. Instead that will be handled for us by the component.
All we need to do is implement the loadInRange method so that the component can call it and receive the posts on a user's timeline.
Based on the query code in the viewDidAppear method, can you come up with an implementation of loadInRange?
We start by calling the timelineRequestForCurrentUser method. Earlier we have extended the method to take a range parameter. We now simply pass on the range that we received in the range argument.
In the callback of the query we check whether or not we have received a result. If the result is nil we store an empty array in the posts variable.
We pass the posts that have been loaded back to the TimelineComponent by calling the completionBlock.
Now our class fully conforms to the TimelineComponentTarget protocol! However, there are a few additional changes we need to make to the TimelineViewController.
Informing the TimelineComponent About Events
There are two events that the TimelineComponent needs to know about in order to do its job correctly:
The component needs to know when the table view becomes visible so that it can load the initial set of data.
The component needs to know which cells are currently being displayed so that it can load more cells as soon as the user has reached the latest cell in the Timeline.
Let's start by informing the component when the table view becomes visible.
Triggering the Initial Timeline Request
The TimelineComponent wants us to call the loadInitialIfRequired() method when that happens. We can implement that method call inside of the viewDidAppear method, which is called as soon as the table view becomes visible.
Since we have implemented the timeline query inside of the loadInRange method, we also no longer need it in the viewDidAppear method. So this is a good chance to clean that method up.
Change the viewDidAppear method of the TimelineViewController class to look as following:
Don't worry, we will declare timelineComponent in just a moment. Now the TimelineComponent will make an initial timeline request, if no data has been loaded so far. If the component has already queried the server and stored a user's posts, this method call does nothing at all. After the initial load, posts will only be reloaded if the user manually chooses to do so (by using the pull-to-refresh mechanism).
Next we need to inform the TimelineComponent which cell is currently visible.
Informing the TimelineComponent about visible cells
Whenever a cell becomes visible, we are required to call the targetWillDisplayEntry: method and pass the index of the currently displayed entry.
Where can we place the code that calls that method?
One option would be the cellForRowAtIndexPath: method that is called whenever the table view requests a cell. However, there are some cases where this method is called but the requested cell is not actually displayed - so this solution could lead to some bugs in our app.
Instead, there's a method that's part of the UITableViewDelegate protocol that is perfect for our purposes:
To keep our code well structured, we implement the new protocol in a separate class extension. The implementation of this method is fairly simple - we directly call the timelineComponent and inform it that a cell has been displayed.
Note that this code won't work yet. First, we haven't declared the timelineComponent property yet. Second, the TimelineViewController isn't the delegate of the table view yet. Currently, it is only the dataSource.
Let's set ourselves up as the table view's delegate. After that, we'll initialize and store the TimelineComponent.
Open Main.storyboard and connect the outlet of the table view's delegate to the TimelineViewController:
Initializing and storing the TimelineComponent
Now let's create the property that will store the TimelineComponent object.
Add the following property to the TimelineViewController class:
Note that you need to provide two different types in the angled brackets: the type of object you are displaying (Post) and the class that will be the target of the TimelineComponent (that's the TimelineViewController in our case).
Next we will add code that creates an instance of the TimelineComponent. We'll add that to the viewDidLoad method. As soon as our view is loaded, we want the TimelineComponent to be available:
Extend the viewDidLoad method so that it initializes a TimelineComponent
The TimelineComponent only takes one argument when it's being initialized: the target. The target is the object to which the TimelineComponent shall add its functionality. In our case that's the TimelineViewController, so we pass self to the initializer.
Removing the posts array
One last step remains! We need to remove the posts property that currently stores all the posts displayed on the timeline. Now the TimelineComponent will take care of storing them!
Delete the posts property of the TimelineViewController class
This means we also need to modify two locations where this property was used. Instead of accessing the posts property we now need to access the timelineComponent.content property.
Change the tableView(_, numberOfRowsInSection:) method to look as following:
Okay, time to test our app after these major changes! Your timeline now should be working just as shown
in the video below:
When you reach the 5th post, additional posts are loaded and displayed!
In this chapter, you learned two important concepts: First, we modified our timeline query to only load a certain range of posts. That is extremely important! It is very inefficient to load data that the user doesn't need; using the skip and limit properties of PFQuery you will be able to load exactly the portion of your data that is currently relevant to your user.
You have also learned how to use the TimelineComponent. If you happen to build an app that contains a timeline, this component will be very useful!
In the next step we will take a little step back and review all the things you have learned through this tutorial so far.