I have TableView with customs cell representing events. It looks very close to first and third image here.
As you can see (sorry for small resolution) on some events there are photos of friends that are going to participate. Unfortunately information about friends is not loaded with other information about events.
So after I got list of events I can make request to load list of friends that are going to participate in each event.
Right now I use code like
class EventCell : UITableViewCell {
var eventForCell : Event? {
didSet {
eventTitleLabel.text = eventForCell.title
eventDateLabel.text = eventForCell.date
presentFriends(eventID : eventForCell.id)
}
}
func presentFriends(eventID : Int) {
//searching for friends for event with specific eventID
.......
.......
//for every friend found adding UIImageView in cell
for friend in friendsOnEvent {
let avatar = UIImageView(....)
self.addSubview(avatar)
}
}
}
This code works but photos are not presented in smooth way. Also if you scroll list fast they start to blink. Maybe it is even not necessary to load them if user scrolls fast list of events. So I have two questions:
Update:
I found info about tableView(_:prefetchRowsAt:) method inside UITableViewPrefetchingDataSource protocol, should I use it for this case? Maybe someone has some experience with it?
1. (Re)creating a view objects during cellForRowAt
is generally a bad practice. From the screenshot I assume that there is a limit to how many avatars are there on a single cell - I would recommend creating all the UIImageView
objects in the cell initializer, and then in presentFriends
just set images to them, and either hide the unused ones (isHidden = true
) or set their alpha to 0 (of course, don't forget to unhide those that are used).
2. If you are using SDWebImage to load images, implement prepareForReuse
and cancel current downloads to get a bit of performance boost during scrolling, and prevent undefined behaviour (when you try to set another image while the previous one was not yet downloaded). Based on this question, this one and this one I would expect something like:
override func prepareForReuse() {
super.prepareForReuse()
self.imageView.sd_cancelCurrentImageLoad()
}
Or you can use [this gist][4] for an inspiration.
P.S.: Also, you will have to count with some blinking, since the images are downloaded from web - there will always be some delay. By caching you can get instantly those that were already downloaded, but new ones will have delay - there is no way to prevent that (unless you preload all the images that can appear in tableView before presenting tableView).
P.S.2: You can try to implement prefetching using [UITableViewDataSourcePrefetching][6]
. This could help you out with blinking caused by downloading the avatars from web. This would make things a bit more complicated, but if you really want to remove that blinking you will have to get your hands dirty.
First of all, as you can see from the documentation, prefetchRowsAt
does not give you a cell object - thus you will have to prefetch images to your model object instead of simply using sd_setImage
on the UIImageView
object at a given cell. Maybe the aforementioned gist would help you out with downloading images to model.
Now also as the documentation states:
Loading Data Asynchronously
The
tableView(_:prefetchRowsAt:)
method is not necessarily called for every cell in the table view. Your implementation oftableView(_:cellForRowAt:)
must therefore be able to cope with the following potential situations:Data has been loaded via the prefetch request, and is ready to be displayed.
Data is currently being prefetched, but is not yet available.
Data has not yet been requested.
Simply said, when you are in cellForRowAt
, you cannot rely on prefetchRowsAt
being called - it might have been called, and it might not have been called (or maybe the call is in progress).
Documentation continues to suggest using Operation
as a backing tool for downloading the resources - prefetchRowsAt
could create Operation
objects that would be responsible for downloading avatars for a given row. cellForRowAt
can then ask the model for the Operation
objects for the given row - if there are not any, it means that prefetchRowsAt
was not called for that row, and you have to create Operation
objects at that point. Otherwise you can use the operations created by prefetchRowsAt
.
Anyway, if you decide that it is worth it, you can use this tutorial as an inspiration for implementing prefetching.