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

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

--

This is part 1 of a 2 part 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. This may be required when either the view is complex — with many layered sub-views that are graphically intense/slow to render — or is dependent upon loading dynamic data of unknown length, such as from a REST interface. The issue is that it looks messy when a screen opens with parts of the screen loading in a random, bit-wise fashion or visually resizing to fit data. We show techniques to delay showing the master view until all data has loaded and sub-views have been rendered. The master view can then be shown — maybe with a transition — with all content ready. Your users will love the smooth appearance of your app screens!

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.

We are going to look at a simple example of a scrolling view of tiles, where each tile has an image and text block associated with it. An example of what the screen will look like is:

This is a ScrollView containing a list of tiles. The source text used in the tile may be a long string of text and we are choosing to truncate the text down to the first few lines to show as a summary. We also want to do something clever, which is limit the height of the tile to accommodate an exact number of lines of the source text (a cruder approach might be to limit the number of characters or words to a fixed number, but there is no way of knowing how many lines this text will then use). This is difficult to do using standard Titanium. If we set the height of the text block to Ti.UI.SIZE, the text block would adjust its’ height to fit the text, which is not what we want. So we do this by first calculating the height of some dummy text — given the font metrics we are using such as font type and size and alignment etc — in a hidden Ti.UI.Label. There have been tickets in Appcelerator JIRA — see for example TIMOB-17340 — for a long time talking about exposing the line height property of text views, but to date this still has not been finished. We do this by simply filling a hidden Label view with 10 lines of text and calculating the rendered height of the view on a “postlayout” event. This is the first of our useful rendering techniques: of using the “postlayout” event— that can be used with any view — to be able to access the view’s actual layed-out properties after it has been rendered. Text is one of the most awkward views to deal with graphically as there is no direct simple correspondence between the pixel dimensions and layout of the text and it’s properties (the font rendering is hidden from us). If the height of the Label is set to Ti.UI.SIZE, you don’t know how high the text view will be until it has been populated with the relevant text and rendered (the default value for the width and height properties of a view, if undefined, is either Ti.UI.SIZE or Ti.UI.FILL and is dependent upon the type of view and its parent view. See the Appcelerator docs for more explanation.). After rendering, the exact pixel dimensions of the view can be obtained from either the (read-only) values of view.size or view.rect. Here is the simplified code to handle the line height calculation of the text blocks:

example.xml:<Alloy>
<Window id=”win”>
<Label id=”hiddenLabel” visible=”false”/>
<ScrollView id=”scrollView” layout=”horizontal” />
</Window>
</Alloy>
example.js://set hidden label text to 10 lines of text..
$.hiddenLabel.text = ‘1\n2\n3\n4\n5\n6\n7\n8\n9\n0’;
function onHiddenLabelPostlayout(_evt) {
$.hiddenLabel.removeEventListener(‘postlayout’, onHiddenLabelPostlayout);
var lineHeight = $.hiddenLabel.size.height / 10;
// do some stuff that uses the line height of the text...
}
$.hiddenLabel.addEventListener(‘postlayout’, onHiddenLabelPostlayout);

We now have a measurement of the line height of the text and can use it to set the height of our tile to exactly fit a fixed number of lines of text.

We now add a set of tiles (corresponding to an itemTile.js controller) to our ScrollView in “updateScrollView” and call it after calculating the line height in “onHiddenLabelPostlayout”. “items” is an array of paired image and text data e.g. [{imagePath: ‘/images/image01.png’, text: ‘Some text’}, …….] etc. Note how we are using layout=”horizontal” for the ScrollView in example.xml, so that when the tiles are added to the view they stack up in 2 columns in sequence (their width is set to half the screen, allowing for a margin, thus creating 2 tile columns — see itemTile.tss for the details).

example.js:function onHiddenLabelPostlayout(_evt) {
$.hiddenLabel.removeEventListener(‘postlayout’, onHiddenLabelPostlayout);
var lineHeight = $.hiddenLabel.size.height / 10;
updateScrollView(lineHeight);
}
function updateScrollView(_lineHeight) {
items.forEach(function (_item) {
var itemTileController = Alloy.createController(‘itemTile’, {
imagePath: _item.imagePath,
text: _item.text,
lineHeight: _lineHeight
});
$.scrollView.add(itemTileController.getView());
});
}

In our tile module we pass the line height as a parameter to the controller:

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 lineHeight = $.args.lineHeight;// fix the number of lines of text
var numberOfLines = 5;
var textHeight = lineHeight * numberOfLines;
$.textLabel.height = textHeight;
//work out height of tile from all its’ sub-views
$.itemTile.height = textHeight + $.textLabel.top + $.paddingFrame.top + $.paddingFrame.bottom + $.imageView.height;

itemTile.tss:
“#paddingFrame” : {
top: 7,
bottom: 7,
....
}
“#imageView” : {
height: 100
}
“#textLabel” : {
width: Ti.UI.FILL,
height: Ti.UI.SIZE,
top: 5
....
}

I haven’t included all the code details in the inserts above, just the basics to follow the logic. We choose to set the height of the text Label to 5 lines of text and have also created a frame around the image and text content for a surrounding margin. Note that Android is a bit weird (isn’t it always?) and doesn’t measure the line height in exactly the same sensible way as for iOS, so we have to allow for that by a fudge of adding a bit extra to the height (see the example code on GitHub for details).

So this then addresses the issue of delaying rendering dependent upon text length and dimensions. In Part 2 of this tutorial we will go on to look at other delays in rendering due to, for example, data loading.

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.

--

--