Implementing Annotations for Elements in a Mobile App
We develop annotation systems for mobile apps — not just a chat overlay on content, but binding a comment to a specific point on an image, document, or list item. We have 5+ years of experience in this niche and have delivered over 20 projects with annotations for iOS, Android, and Flutter. Here we share the key technical solutions that ensure stability and performance.
How to Normalize Coordinates for Different Screens?
The core problem: a user taps an image on an iPhone SE with one screen size, while another views the same document on an iPad Pro in landscape. The pin must point to the same spot. The solution: store relative coordinates, not absolute pixels — x and y as a fraction of the container width and height (from 0.0 to 1.0). On rendering, multiply by the actual container size. On iOS: CGPoint(x: pin.relativeX * containerWidth, y: pin.relativeY * containerHeight). On Flutter: similarly using Positioned inside a Stack with computed left and top.
For documents with zoom, it gets more complex: you need to account for contentOffset and zoomScale of UIScrollView. We store the coordinate in content space, and when displaying, convert to screen coordinates using UIScrollView.convert(_:to:). This ensures pins don't "drift" when zooming. A typical bug: calculating in viewport coordinates instead of content. We fix it by adding scrollViewDidZoom to force positions update.
How to Implement Real-Time Annotation Synchronization?
Each pin is an object with fields: id, contentId, relativeX, relativeY, authorId, createdAt, text, resolved. The latter is important: the ability to mark a comment as resolved is a standard feature for review tools.
For synchronization between users, we use WebSocket (Socket.io or native URLSessionWebSocketTask). A new pin appears instantly for everyone viewing the same document. Optimistic update: add the pin to local state immediately, send the request, rollback on error. For offline scenarios: Core Data or SQLite with a pendingSync flag. On reconnection — batch sync via REST.
Comparison of Coordinate Storage Approaches
| Approach | Description | Advantages | Disadvantages |
|---|---|---|---|
| Absolute pixels | Fixed x,y in pixels | Simple implementation | Doesn't work on different screens |
| Relative coordinates | x,y as fraction of width/height | Scalable across screens | Requires normalization |
| Content space coordinates | Internal document coordinate system | Correct under zoom and scroll | More complex conversion |
Relative coordinates with content space is the optimal choice. Pin clustering reduces UI load by 10x compared to rendering all markers when there are 50+ points.
Pin UI Component
A pin on screen is a UIView (or View in SwiftUI / widget in Flutter) with absolute positioning. Some practical details:
- Pins must not overflow the container. When
relativeX > 0.95, push the tooltip to the left edge; when< 0.05, to the right. Similarly vertically. Simple logic, but without it the tooltip goes off-screen. - If there are many pins (50+), rendering all at once is not advisable. Use clustering: at small zoom, group nearby pins into a cluster with a count. Expand on zoom. On iOS —
MKClusterAnnotationas a pattern (even if not working with maps). On Flutter — manual clustering viaquadtreeor the libraryflutter_map_marker_cluster.
Typical Implementation Mistakes
- Storing coordinates in viewport pixels — pins "drift" on zoom.
- Ignoring offset and zoomScale of ScrollView — pin position mismatches after scrolling.
- No optimistic UI — user waits for server response.
- Rendering all pins at once when 100+ — FPS drops.
- Deep threads (more than 2 levels) — inconvenient on mobile.
Comment Thread
A single pin can have multiple replies — a thread is needed. We implement via parentId: root comments have parentId: null, replies reference the parent. We don't go deeper than one level of nesting in mobile UI — it's inconvenient.
The thread component opens as a bottom sheet (iOS: UISheetPresentationController with .medium and .large detents; Flutter: DraggableScrollableSheet). This doesn't cover the full screen and doesn't lose pin context.
What's Included in the Work
- Pin component with normalized coordinates and zoom support
- Add/edit comment form
- Reply thread in a bottom sheet
- "Resolved" status with visual differentiation
- REST API integration + optional WebSocket sync
- Pin clustering for large numbers
- Support for images, PDFs, arbitrary View containers
Timelines and Guarantees
Basic implementation (pins on image, no threads or sync): 2 days. Full version with threads, real-time sync, and clustering: 4–5 days. Cost is calculated individually after analyzing requirements and existing API.
Contact us to evaluate your project. We guarantee quality and adherence to deadlines. Order turnkey development — get a ready-made solution adapted to your stack.







