Dynamically Loading Table View Images in iOS
Learn how thumbnail images in the table views of iOS mobile apps can be loaded dynamically as needed to populate table view cells.
Join the DZone community and get the full member experience.
Join For Freeios applications often display thumbnail images in table views alongside other text-based content such as contact names or product descriptions. however, these images are not usually delivered with the initial request, but must instead be retrieved separately afterward. they are typically downloaded in the background as needed to avoid blocking the main thread, which would temporarily render the user interface unresponsive.
for example, consider this rest service , which returns a list of simulated photo data:
[
{
"albumid": 1,
"id": 1,
"title": "accusamus beatae ad facilis cum similique qui sunt",
"url": "http://placehold.it/600/92c952",
"thumbnailurl": "http://placehold.it/150/92c952"
},
{
"albumid": 1,
"id": 2,
"title": "reprehenderit est deserunt velit ipsam",
"url": "http://placehold.it/600/771796",
"thumbnailurl": "http://placehold.it/150/771796"
},
{
"albumid": 1,
"id": 3,
"title": "officia porro iure quia iusto qui ipsa ut modi",
"url": "http://placehold.it/600/24f355",
"thumbnailurl": "http://placehold.it/150/24f355"
},
...
]
each record contains a photo id, album id, and title, as well as urls for both thumbnail and full-size images; for example:
photo class
a class representing this data might be defined as follows:
class photo: nsobject {
var id: int = 0
var albumid: int = 0
var title: string?
var url: url?
var thumbnailurl: url?
init(dictionary: [string: any]) {
super.init()
setvaluesforkeys(dictionary)
}
override func setvalue(_ value: any?, forkey key: string) {
switch key {
case #keypath(url):
url = url(string: value as! string)
case #keypath(thumbnailurl):
thumbnailurl = url(string: value as! string)
default:
super.setvalue(value, forkey: key)
}
}
}
since instances of this class will be populated using dictionary values returned by the web service, an initializer that takes a dictionary argument is provided. the
setvaluesforkeys(_:)
method of
nsobject
is used to map dictionary entries to property values. the id and
title
properties are handled automatically by this method; the class overrides
setvalue(_:forkey:)
to convert the urls from strings into actual
url
instances.
view controller class
a basic user interface for displaying service results in a table view is shown below:
row data is stored in an array of
photo
instances. previously loaded thumbnail images are stored in a dictionary that associates
uiimage
instances with photo ids:
class viewcontroller: uitableviewcontroller {
// row data
var photos: [photo]!
// image cache
var thumbnailimages: [int: uiimage] = [:]
...
}
the photo list is loaded the first time the view appears. the
wswebserviceproxy
class of the open-source
http-rpc
library is used to retrieve the data. if the call succeeds, the response data (an array of dictionary objects) is transformed into an array of
photo
objects and the table view is refreshed. otherwise, an error message is displayed:
override func viewwillappear(_ animated: bool) {
super.viewwillappear(animated)
// load photo data
if (photos == nil) {
let serviceproxy = wswebserviceproxy(session: urlsession.shared, serverurl: url(string: "https://jsonplaceholder.typicode.com")!)
serviceproxy.invoke("get", path: "/photos") { result, error in
if (error == nil) {
let photos = result as! [[string: any]]
self.photos = photos.map({
return photo(dictionary: $0)
})
self.tableview.reloaddata()
} else {
let alertcontroller = uialertcontroller(title: "error", message: error!.localizeddescription, preferredstyle: .alert)
alertcontroller.addaction(uialertaction(title: "ok", style: .default))
self.present(alertcontroller, animated: true)
}
}
}
}
cell content is generated as follows. the corresponding
photo
instance is retrieved from the
photos
array and used to configure the cell:
override func tableview(_ tableview: uitableview, numberofrowsinsection section: int) -> int {
return (photos == nil) ? 0 : photos.count
}
override func tableview(_ tableview: uitableview, cellforrowat indexpath: indexpath) -> uitableviewcell {
var cell = tableview.dequeuereusablecell(withidentifier: cellidentifier)
if (cell == nil) {
cell = uitableviewcell(style: .default, reuseidentifier: cellidentifier)
}
let photo = photos[indexpath.row];
cell!.textlabel!.text = photo.title
// attempt to load image from cache
cell!.imageview!.image = thumbnailimages[photo.id]
if (cell!.imageview!.image == nil && photo.thumbnailurl != nil) {
// image was not found in cache; load it from the server
...
}
return cell!
}
if the thumbnail image is already available in the cache, it is used to populate the cell's image view. otherwise, it is loaded from the server and added to the cache as shown below. if the cell is still visible when the image request returns, it is updated immediately and reloaded:
let serverurl = url(string: string(format: "%@://%@", photo.thumbnailurl!.scheme!, photo.thumbnailurl!.host!))!
let serviceproxy = wswebserviceproxy(session: urlsession.shared, serverurl: serverurl)
serviceproxy.invoke("get", path: photo.thumbnailurl!.path) { result, error in
if (error == nil) {
// if cell is still visible, update image and reload row
let cell = tableview.cellforrow(at: indexpath)
if (cell != nil) {
if let thumbnailimage = result as? uiimage {
self.thumbnailimages[photo.id] = thumbnailimage
cell!.imageview!.image = thumbnailimage
tableview.reloadrows(at: [indexpath], with: .none)
}
}
} else {
print(error!.localizeddescription)
}
}
finally, if the system is running low on memory, the image cache is cleared:
override func didreceivememorywarning() {
super.didreceivememorywarning()
// clear image cache
thumbnailimages.removeall()
}
summary
this article provided an overview of how images can be dynamically loaded as needed to populate table view cells in ios. for more ways to simplify ios app development, please see my projects on github:
Published at DZone with permission of Greg Brown, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments