Scratching image table view controllers

The aim of this post is showing how to fix choppy tableviews when they are showing huge images fetched from the cloud.

 

The App

The app is very simple, just a table view that shows a collection of pictures.

Simulator Screen Shot 28 Aug 2016 17.03.55

The app presents the following problems:

  • The picture size is huge so the scroll must be smooth.
  • There is a high memory consumption, so iOS quick the app out.
  • The pictures are fetched every time that a cell is being presented (not cached).
  • Fetch is not cancelled once the cell is not shown in the tableview due to scroll.

filesizes

The picture resolution is 6144 x 4096.

 

The classical approach

Tableviews (and scrollviews) scroll must be smooth. User would not detect app bandwidth consumption easily, but a bumpy scroll is detected from the very beginning. Here what we have to do is to be sure that image is fetched in background and, once is retrieved, update image view in the main thread.

This is how a regular cell should have to be configured:

 

Memory consumption

But memory consumption increases in a dramatic (deadly) way:

MemoryConsumption

When you access the NSData, it is often compressed (with either PNG or JPEG). When you use the UIImage, there is an uncompressed pixel buffer which is often 4 bytes per pixel (one byte for red, green, blue, and alpha, respectively). There are other formats, but it illustrates the basic idea, that the JPEG or PNG representations can be compressed, when you start using an image is uncompressed.

This model does not work at all with huge pictures, it is  necessary to work with a different architecture.

The image provider

We will delegate the  work of fetching and processing the image to a image provider class. Every cell will has its own image provider.

imageprovider

We will create the image provider in tableview willDisplayCell method:

It could be the case that cell will dissapear before image provider ends its tasks. You can cancel the operation done by image provider.

In my case I was not interested in doing that because it was highly likely to get back to the dissapeared cell.

Cache the downloaded stuff.

At the end is only necessary to download the image once, not every time that cell is going to be shown. We have implemented this by using NSCache object, this differs from other mutable collections in a few ways:

  • It incorporates various auto-removal policies, which ensures that it does not use too much system memory.
  • You can access from different threads without having to lock the cache yourselve.
  • Retrieving something from an NSCache object returns an autoreleased result.
  • Unlike an NSMutableDictionary object, a cache does not copy the key objects that are put into it.

Profile new architecture

With new architecture the memory consumption lasts in that way:

newmetrics

Awesome, It has been reduced memory consumption by 60!!!

 

Conclusion

There are times that is not under app developer the image size of images that has to fetch. If you use the classical approach then you will find that tableview scroll is choppy and most probaly OS will kick the app out. For avoiding such disgusting issue this architecture fits properly well. You can find the source code here.

     

A dropdown view with Autolayout (and a few lines of code)

The aim of this post is just to explain how simple is to make a drowdown view controller using Autolayout (… and a few lines of code also). But believe be, the magic is done by Autolayout. At the end you will have a view controller that will allow to drop down the upper view:

Final

Setup the project

Create a brand new Single View Application project:

Screen Shot 2015-12-23 at 13.46.09

Be sure that Language is swift:

Screen Shot 2015-12-23 at 13.49.35

In the default View controller add two views, one in the top-half and the other in the bottom-half. Finally add a button in the middle of two views:

Screen Shot 2015-12-23 at 14.04.24

I have added some color in the view components just for better clarification.

Autolayout

Top subview. Add the following 5 constraints:

Screen Shot 2015-12-23 at 14.07.19

 

Bottom subview. Add the following 4 constraints:

Screen Shot 2015-12-23 at 14.09.26

 

Button. Add the following 3 constraints:

Screen Shot 2015-12-23 at 14.12.02

Screen Shot 2015-12-23 at 14.13.52

 

Button – Bottom subview. For the following constraint do not forget select Top subview and Button.

Screen Shot 2015-12-23 at 14.15.52

And apply the following constraint:

Screen Shot 2015-12-23 at 14.18.58

Autolayout is giving to you a warning because he excepts that Button and Top subview would be bottom alligned.:

Screen Shot 2015-12-23 at 14.19.40

Our intention is leave the buttom in the middle of two views, so we have to update constraint:

Screen Shot 2015-12-23 at 14.25.10

 

… and a few lines of code

Outlets

 

Now link outlets:

  • btnFoldButton to the button
  • cnsUpperViewHeight to the Top view height constraint.

 

Add pan gesture recognizer to Button:

What we do in the gesture recognizer is just calculate new value for cnsUpperViewHeight constraint, the hard work is performed by autolayout.

 

Finally

Let’s make some make up, just adding some picture at the top subview and a map at the bottom view.

For the picture and the map add the following constraints:

Screen Shot 2015-12-23 at 14.46.35

 

For the map do not forget the import MapKit in the view controller code:

Screen Shot 2015-12-23 at 14.53.20

Finally run the project:

Screen Shot 2015-12-23 at 14.56.24

 

Pan the button and you will see how top view is compressed, giving more room for bottom view.

You can also download the sample project from gitHub.

Variant

If what you really want is just shift the upper view and avoid compress effect on the top view, just remove the top constraint on the picture that is placed at the top subview:

 

Screen Shot 2015-12-23 at 15.03.23

This will be what you get:

Screen Shot 2015-12-23 at 15.04.26

Useful links: