Ive already talked about the basics of a Firebase social network project, and adding posts in two other articles. Here I want to explain a method for loading those posts from a Firebase array, streaming them in chunks to a UITableView in an 'infinite' way.
To explain more, the scenario is : you have an array of items in your Firebase database - perhaps hundreds or thousands. You want to display the latest bunch of items, but you don't want to go and load the lot of course, you just want to load enough to populate the visible part of your table. But as the user then scrolls down the table, you want the next set of items to be loaded in as required, on and on until the oldest item in the database is displayed, and the table stops scrolling. This is quite easily achievable without too much code - heres how I do it.
In this example the items I am loading are posts from a social network. From now on I refer to the downloaded items as posts, and my local type to hold them is called GZPost. In terms of class properties on my UIViewController, theres three I need. This snippet has the three properties plus some setup items for my UITableView. Comments explain what the properties are for.
My 'reloadPost' func is mentioned in the refreshOptions above. Heres the code for that :
This is fairly self explanatory - empty the posts array, set the loadingPosts count to zero (more on that later), and reload the UITableView to empty it of previous cells, before we start loading again.
The main work here is done by my 'loadSomeMorePosts' func, the code for which is here.
This method is called every time the UITableView scrolls and we need more data to fill the cells with posts.
First its important to picture this : the data in our Firebase database is ordered in terms of newest post last - so in date order from old to new. In our UITableView, we want to see this order reversed, because we want the newest post at the top of the table.
For our query then, to fill the top of the table first, we must go to the very end of our posts array in the database, and get the last posts. This ensures that the newest posts (those at the end of the array) are the ones we see at the top of the table. Last = newest.
Then as we fill further down the table, we are effectively going up that Firebase array from the bottom to the top. So that we know where we are up to as we go, we have a class property called 'idOfFinalPostInNextQuery'. When we first query the database, we get the last six posts from the array - the six most recent posts. But we dont display all 6 : we display 5, and the oldest of the bunch, we save the key of into that property. Then when we next need to load some posts, we go from that key backwards up the array, until we finally get to the top of the array, which is the oldest post.
At the top of the code snippet above, you first see the creation of the DatabaseQuery object, which is a Firebase type. 'QueryOrderedByKey' also ensures they are ordered by date, as the automatically created keys do this by default. You can see what I have described above in the next line, where the 'queryEnding' property on that query is set to 'idOfFinalPostInNextQuery' - this ensures that second time and beyond that, we dont go from the end of the array, but from that last point backwards.
Now a word on 'loadingPostCount' (line 10 in the above code snippet). The value of this at any time, is equal to the number of posts we already have loaded and in cells in the table, plus the number of posts that we are currently querying (and possibly waiting) for. It effectively shows the total number of posts. Later on theres a tableview:willDisplayCell: method, that allows us to trigger a further round of loading of data. We compare this loadingPostCount, to the row number currently being displayed. If we have reached the bottom, we call to load more posts. We cannot simply use self.posts.count for this : because we might not have loaded all the posts we are currently querying for.
Ok so the main chunk above, line 12 : queryLimited(toLast .. . This is the call to the Firebase database. We load one more than the number of posts we have asked to load - because we save one of these as the marker point for where we next will need to load more data. You see the code for that on lines 15-17 - we set the idOfFinalPostInNextQuery property, then remove it from the array so it isnt displayed this time.
Then we reverse the posts because we want newest first, and our downloaded data is ordered as newest last. Line 20 onwards : those reversed snapshots are iterated through. We check to see if we already have the given post id in our posts array. If we do, we update the post, and otherwise we load it.
This is fairly self explanatory - take the snapshot and turn it into our local Post type, GZPost. Create an index path, add or replace the post in our local posts array, then either replace or add the new rows to our UITableView.
Finally, heres the UITableView datasource / delegate methods. The 'numberOfRowsInSection' is simply the count of our local Posts array. The cellForRow gets a customized cell type I made, and calls a func I created on it - configureCell, which simply puts the data into the cell. Then at the bottom you see the bit that makes the table load new data as you go. the willDisplayCell: method, which checks the indexPath of the cell that is about to be displayed, and compares it to the loading post count. If we are about to run out of data, off we go to load more.
Thats everything. Please do comment if you would like further explanation of anything.
Thursday, April 19, 2018
Friday, April 13, 2018
Closures in function definitions : Creating a 'completionHandler' in Swift
Heres an overview of using a closure in a function definition, that gives you your own 'completionHandler' for functions in Swift 4. The end result I am talking about is something like this :
A completion handler is just a closure, and it is given the name 'completionHandler' in the function definition. A closure is a chunk of code that can be passed into a function, just like a property can, to be executed later in the course of that function.
Looking at the code snippet above, just after the 'completionHandler:' part of the call, I have a closure - thats the part in curly braces. The closure itself is passed into the function like a property in a way, along with all the other properties I might send into the function.
So how is that function defined, so that I can pass that closure in, call it a 'completionHandler', and get it to execute and a given time during that function call?
First, I need to know the kinds of properties I want to have returned from the call, for my closure code to use (the 'result, ref in' part). In my case, I wanted a true or false as to whether my network call had succeeded. Should it have worked, I wanted to know the database reference that the network call returned.
So I'm looking to get back a Bool property, and a Firebase type called DatabaseReference. As I say, this was just for my particular task - obviously you can return any amount of properties for your closure to use.
So heres the function definition itself to make that work :
Thats the whole thing. Breaking it down, the part that allows the closure to be passed in is this:
In a nutshell, I am later able to begin my closure with '{ result, ref in', because I have defined those types in the line of code above, as (Bool, DatabaseReference). The 'result' property is the Bool, and the 'ref' property is the DatabaseReference.
Just after that, I have '-> Void'. This just completes the definition of the type of closure I can send it. In this case, the closure is not expected to return anything to the method that calls it, such as 'return true'. Nothing is returned at all, ie - 'Void', so we dont have to 'return' in the closure statement.
Ignore the line of code with Database.database().. etc. Thats just the main task of my particular function in this case, which updated an online Firebase database. The important bit comes just after that, where we call the completionHandler closure to execute it.
To do that we simply call the 'name' of that closure as defined in the function - we gave it the name 'completionHandler' in the function definition. So we execute the closure by calling that named property, and we set the 'result' and 'ref' properties just after we call it. Like this :
And thats when the closure gets executed, with those properties that we wanted set. And thats it. Theres more to closures of course, but this achieves most of what you need most of the time.
A completion handler is just a closure, and it is given the name 'completionHandler' in the function definition. A closure is a chunk of code that can be passed into a function, just like a property can, to be executed later in the course of that function.
Looking at the code snippet above, just after the 'completionHandler:' part of the call, I have a closure - thats the part in curly braces. The closure itself is passed into the function like a property in a way, along with all the other properties I might send into the function.
So how is that function defined, so that I can pass that closure in, call it a 'completionHandler', and get it to execute and a given time during that function call?
First, I need to know the kinds of properties I want to have returned from the call, for my closure code to use (the 'result, ref in' part). In my case, I wanted a true or false as to whether my network call had succeeded. Should it have worked, I wanted to know the database reference that the network call returned.
So I'm looking to get back a Bool property, and a Firebase type called DatabaseReference. As I say, this was just for my particular task - obviously you can return any amount of properties for your closure to use.
So heres the function definition itself to make that work :
Thats the whole thing. Breaking it down, the part that allows the closure to be passed in is this:
In a nutshell, I am later able to begin my closure with '{ result, ref in', because I have defined those types in the line of code above, as (Bool, DatabaseReference). The 'result' property is the Bool, and the 'ref' property is the DatabaseReference.
Just after that, I have '-> Void'. This just completes the definition of the type of closure I can send it. In this case, the closure is not expected to return anything to the method that calls it, such as 'return true'. Nothing is returned at all, ie - 'Void', so we dont have to 'return' in the closure statement.
Ignore the line of code with Database.database().. etc. Thats just the main task of my particular function in this case, which updated an online Firebase database. The important bit comes just after that, where we call the completionHandler closure to execute it.
To do that we simply call the 'name' of that closure as defined in the function - we gave it the name 'completionHandler' in the function definition. So we execute the closure by calling that named property, and we set the 'result' and 'ref' properties just after we call it. Like this :
And thats when the closure gets executed, with those properties that we wanted set. And thats it. Theres more to closures of course, but this achieves most of what you need most of the time.
Subscribe to:
Posts (Atom)
Social Network with Firebase 3 - Infinitely scrolling UITableView of Firebase Items
Ive already talked about the basics of a Firebase social network project , and adding posts in two other articles. Here I want to explain ...
-
Ive already outlined my database structure for my Firebase based social network. Ive done the auth for new users , and Ive added each new ...
-
Im doing an awful lot of work at the moment with AVMutableVideoCompositions, and AVAssetExportSessions. When exporting a video using the la...