Tuning Titanium Maps: Custom Annotations, Clustering, and Performance on iOS & Android
Learn how to keep Titanium’s Ti.UI.Map responsive by using clustering, annotation pooling, and careful batching. A step‑by‑step guide with code examples and performance tips for iOS and Android.
17 Jun 2026, 01:29 UTC

When a map turns into a performance nightmare
Imagine you’re building a travel app that pulls a list of thousands of points of interest from a server. You drop a Ti.UI.Map into your window, add every annotation, and the app freezes. The problem is not the map itself but how you use the Ti.UI.Map API. This post walks through a concrete workflow that keeps the map responsive, explains why clustering matters, and shows the exact code you need to start right away.
Thesis: Use clustering and annotation pooling to keep Ti.UI.Map snappy on both platforms
On iOS and Android, Ti.UI.Map exposes a common API, but the underlying native views handle large annotation sets differently. The key to good performance is to avoid adding every point to the map at once. Instead, add a manageable number of annotations, enable clustering on supported OS versions, and reuse annotation objects when the view recycles them.
Section 1 – Adding a Map and a Custom Annotation
Below is a minimal example that shows how to create a map, add a single custom annotation, and listen for click events. The snippet runs on both iOS and Android, but note the platform‑specific pitfalls.
// app.js – run with titanium run (requires a valid API key on Android)
var win = Ti.UI.createWindow({
backgroundColor: '#fff',
title: 'Map Demo'
});
var map = Ti.UI.createMap({
top: 0, left: 0, right: 0, bottom: 0,
mapType: Ti.UI.Map.STANDARD_TYPE,
animate: true,
region: {
latitude: 48.8584, // Eiffel Tower
longitude: 2.2945,
latitudeDelta: 0.05,
longitudeDelta: 0.05
},
userLocation: true
});
win.add(map);
win.open();
var annotation = Ti.UI.createMapAnnotation({
latitude: 48.8584,
longitude: 2.2945,
title: 'Eiffel Tower',
subtitle: 'Paris, France',
pincolor: Ti.UI.Map.ANNOTATION_RED,
leftButton: Ti.UI.Android ? null : Ti.UI.createImageView({image: 'icon.png'}),
rightButton: Ti.UI.Android ? null : Ti.UI.createImageView({image: 'icon.png'})
});
map.addAnnotation(annotation);
map.addEventListener('click', function(e) {
if (e.clicksource === 'annotation') {
Ti.API.info('Annotation clicked: ' + e.title);
}
});
**Key points**:
- On Android,
regionis read‑only; you must usesetRegionorsetRegionAnimatedinstead of setting the property directly. - The
leftButtonandrightButtonproperties are only supported on iOS. The example uses a conditional to avoid errors on Android. - Always include a
userLocationflag if you want to show the current position; remember to add the required permission keys intiapp.xmlfor both platforms.
Section 2 – Clustering: A Performance‑First Decision
When you have more than a few dozen annotations, the native map view can become sluggish. iOS 11+ and Android 6+ provide built‑in clustering. Titanium exposes this through the clustered property on the map object.
map.clustered = true; // Enables clustering on supported OS versions
To verify clustering works:
- Run the app on a device with iOS 11+ or Android 6+.
- Add 200+ annotations programmatically (see the next subsection).
- Zoom out to see that points merge into clusters.
**Trade‑off**: Clustering is not available on older OS versions, so you’ll see individual pins instead. If your user base includes such devices, consider a graceful fallback: show a simplified list view or a static map image.
Section 3 – Annotation Pooling and Batching
Even with clustering, adding thousands of annotations in one go can spike memory usage. A common pattern is to add annotations in batches and reuse annotation objects when the map recycles them.
// Example: batch add 500 annotations in groups of 50
var annotations = [];
for (var i = 0; i < 500; i++) {
var lat = 48.8584 + Math.random() * 0.1 - 0.05;
var lng = 2.2945 + Math.random() * 0.1 - 0.05;
annotations.push(Ti.UI.createMapAnnotation({
latitude: lat,
longitude: lng,
title: 'Spot ' + i,
pincolor: Ti.UI.Map.ANNOTATION_GREEN
}));
}
// Add in batches
var batchSize = 50;
for (var start = 0; start < annotations.length; start += batchSize) {
var batch = annotations.slice(start, start + batchSize);
map.addAnnotations(batch);
// Allow the UI thread to process between batches
Ti.UI.Android ? Ti.Platform.openURL('about:blank') : setTimeout(function(){}, 0);
}
**Why this matters**: The map view internally keeps a pool of annotation views. By reusing objects and limiting the number of active annotations, you reduce the pressure on the garbage collector and avoid UI thread stalls.
Section 4 – Diagnostics & Monitoring
Use the Titanium profiler to watch memory usage while adding many annotations. On Android, run:
adb shell dumpsys meminfo $(pidof com.yourcompany.yourapp)
On iOS, use Instruments’ Allocations and Leaks tools. Look for:
- Large spikes in “Live Bytes” when a batch is added.
- Repeated allocation of
MKAnnotationView(iOS) orView(Android) objects.
If you see memory growth without a corresponding drop, consider increasing batchSize or enabling clustering earlier.
Conclusion – Actionable Checklist
- Enable
clustered = trueon supported OS versions. - Batch annotation additions and reuse annotation objects.
- Use
setRegionAnimatedfor programmatic zoom to avoid setting read‑onlyregionon Android. - Verify permissions in
tiapp.xml(Google Maps API key, NSLocationWhenInUseUsageDescription). - Profile memory on both platforms and adjust
batchSizeor clustering settings accordingly.
By following these steps, you’ll keep the map responsive, reduce crashes, and provide a smooth user experience across iOS and Android.