Delayed Rendering and Dynamic Text Block Sizing in Titanium — Part 2

Simon Buckingham
All Titanium
Published in
5 min readOct 13, 2017

--

This is Part 2 of a tutorial that shows several techniques of how to dynamically set the dimensions of a block of text and handle the delayed rendering of a composite view, in Appcelerator/Axway Titanium. In Part 1 of this tutorial we looked at dynamically sizing a text block after working out the line height of the text using a “postlayout” event. The app example is a list of tiles, consisting of an image and text block each, within a ScrollView. In this part of the tutorial we will look at how to delay the rendering of all our tiles and then smoothly fade on the ScrollView after all our tiles are loaded and rendered.

This article assumes that you have basic knowledge of how to use Titanium and Alloy. The example project was developed using Ti SDK 6.2.2 on OS X 10.12.6 (with XCode 8.3.3) and tested on iOS 10.3 and Android 4 and 6.

As a reminder here is an example of what the screen will look like:

If the images are remote and the text (and maybe even the image location) is supplied as remote data by, for example, a REST interface there will be a delay in loading the data. If we render the ScrollView before the data has loaded we will have lots of empty spaces. Even if we choose to pre-load the data, if we have dozens of tiles to render there will be a delay in rendering just due to the graphics overhead. Bear in mind we have chosen a very simple example, but in the real world the layout of the tile may be much more complicated than this (with, for example, a title, date, icons and who knows what else, as well as an image and text summary in the layout). One possible approach is to render the tiles with their text and leave the images to load one by one (sometimes developers use a loading spinner on each image, although I’m not in favour of this approach as the screen becomes too busy when there are multiple images being loaded). We choose to load and render all tiles in the ScrollView before finally fading it on, thus creating a smooth, seamless transition.

I’m not going to actually program a REST interface and download data remotely, as this is not the point of this tutorial, and so instead will simulate the data load delay with a timeout. We are also going to add a callback to our itemTile.js controller so that we can inform the parent view when the load has completed.

itemTile.xml:<Alloy>
<View id=”itemTile”>
//we use a padding frame to create a margin or padding around our image and text
<View id=”paddingFrame” layout=”vertical”>
<ImageView id=”imageView”/>
<Label id=”textLabel”/>
</View>
</View>
</Alloy>

itemTile.js:
var onRenderedCallback = $.args.onRenderedCallback,
textLoaded = false,
imageLoaded = false;
//pick a random timeout value in a range to simulate
//varying load times for text and images
var timeout1Duration = 1000 * (1 + Math.random() * 3);
var timeout2Duration = 1000 * (1 + Math.random() * 1);
setTimeout(function() {
$.imageView.image = $.args.imagePath || ‘’;
imageLoaded = true;
if (textLoaded && onRenderedCallback) {
onRenderedCallback();
}
}, timeout1Duration);
setTimeout(function() {
$.textLabel.text = $.args.text || ‘’;
textLoaded = true;
if (imageLoaded && onRenderedCallback) {
onRenderedCallback();
}
}, timeout2Duration);
.....etc

We are simulating a race condition where more than one piece of data is being loaded and we don’t know which one will finish first. This is typical with asynchronous web calls. In this simple example it is highly likely the text will load quicker than the image, but in a more complex example we may have all sorts of data and maybe even more than one image to load. So from the code above we see that the callback “onRenderedCallbackis only called when both pieces of data have loaded. Upon calling this we can then update the ScrollView in the parent view. In addition we add a loading spinner (ActivityIndicator) to cover the loading and rendering of the tiles and set the opacity of the ScrollView to 0, ready to fade on.

example.xml:<Alloy>
<Window id=”win”>
<Label id=”hiddenLabel” visible=”false”/>
<ScrollView id=”scrollView” layout=”horizontal” opacity=“0.0”/>
<ActivityIndicator id=”activityIndicator” message=”Loading…”/
</Window>
</Alloy>
example.js:var Animation = require(‘alloy/animation’),
numberOfTilesRendered = 0,
FADE_DURATION = 500;
function onRenderedCallback() {
numberOfTilesRendered++;
if (numberOfTilesRendered === items.length) {
fadeScrollViewOn();
$.activityIndicator.hide();
}
}
function fadeScrollViewOn() {
Animation.fadeIn($.scrollView, FADE_DURATION, function () {
$.scrollView.opacity = 1.0;
});
}
.....etc

Recall “items” is our array of data and so upon the callback from each tile it checks whether all tiles have called back and if so hides the loading spinner (ActivityIndicator) and fades on the ScrollView using the standard Alloy animation package (note that we set the ScrollView opacity to 1 at the end of the animation, as properties can behave in unexpected ways if we don’t do this when animating them).

Now in actual practice, for an image, you would listen for the “load” event that is fired when a remote image has been loaded. Such as:

itemTile.js:$.imageView.addEventListener(‘load’, function(_evt) {
imageLoaded = true;
if (textLoaded && onRenderedCallback) {
onRenderedCallback();
}
});
$.imageView.image = $.args.imagePath || ‘’;

And finally it is worth mentioning another more sophisticated technique for monitoring numerous race conditions and data loads using a variable watcher. A variable watcher looks for changes in the value of variables and allows actions to happen when fulfilled. I have used some code by Ian Serlin for this in my lib file variableWatcher.js. Again in such a simple example as ours, there is nothing to be gained from using a variable watcher, but it potentially could be useful for a more complex situation with many simultaneous, asynchronous, data loads. In this case our code is:

itemTile.jsvar VariableWatcher = require(‘variableWatcher’),
//"loaded" is the object variable we are going to watch
loaded = {
image: false,
text: false
};
function checkIfReadyToRender() {
if (loaded.image && loaded.text) {
VariableWatcher.unwatch(loaded, ‘image’);
VariableWatcher.unwatch(loaded, ‘text’);
if (onRenderedCallback) {
onRenderedCallback();
}
}
}
VariableWatcher.watch(loaded, ‘image’, checkIfReadyToRender);
VariableWatcher.watch(loaded, ‘text’, checkIfReadyToRender);
setTimeout(function() {
$.imageView.image = $.args.imagePath || ‘’;
loaded.image = true;
}, timeout1Duration);
setTimeout(function() {
$.textLabel.text = $.args.text || ‘’;
loaded.text = true;
}, timeout2Duration);

You can see the full completed project here on GitHub with 3 examples of the different techniques explored in parts 1 and 2 of this tutorial.

Simon Buckingham is a designer, UX specialist, animator, creative and technical director and developer with more than 20 years experience. He has been making cross-platform iOS/Android mobile apps with Titanium for more than 5 years. Simon runs his own company Icecandy Entertainment, based in London, UK. You can find out more about his digital career at simonbuckingham.me.

--

--