Implementing Comments on Elements (Annotations) in Mobile Apps
Annotations in mobile — not just chat over content. This is binding text or voice comment to specific point on image, document, drawing, or list element. Task technically more interesting than it seems.
Comment Attachment to Coordinates
Key problem — coordinate normalization. User taps image on iPhone SE with one screen size, second user views same document on iPad Pro landscape. Pin should point to same place.
Solution: store not absolute pixels, but relative coordinates — x and y as fraction of container width and height (0.0 to 1.0). On render multiply by actual container size. On iOS this is CGPoint(x: pin.relativeX * containerWidth, y: pin.relativeY * containerHeight). On Flutter — similarly via Positioned inside Stack with calculated left and top.
For documents with zoom harder: account for contentOffset and zoomScale of UIScrollView. Save coordinate in content space, on display convert to screen space via UIScrollView.convert(_:to:).
Typical bug: pins "move" after zoom. Happens because calculation was in viewport coords, not content. After fixing to content-coords and adding scrollViewDidZoom for position update — pins align properly at any zoom.
Storage and Sync
Each pin — object with fields: id, contentId, relativeX, relativeY, authorId, createdAt, text, resolved. Last field important: ability to mark comment as solved — standard review-tool feature.
For real-time user sync use WebSocket (Socket.io or native URLSessionWebSocketTask). New pin appears immediately for all viewing same document. Optimistic update: add pin to local state immediately, send request, on error rollback.
For offline: Core Data or SQLite with pendingSync flag. On reconnect batch-sync via REST.
Pin UI Component
Pin on screen — UIView (or View in SwiftUI / widget in Flutter) with absolute positioning. Several details from practice:
Pins shouldn't protrude beyond container. At relativeX > 0.95 pin tooltip to left edge, at < 0.05 — to right. Similarly vertically. Simple logic, but without it tooltip goes offscreen.
With many pins (50+), rendering all simultaneously unwise. Use clustering: at small zoom group nearby pins into cluster with count. Expand on zoom. On iOS — MKClusterAnnotation as pattern (even if not working with maps). On Flutter — manual clustering via quadtree or flutter_map_marker_cluster library.
Comment Thread
One pin may have multiple responses — need thread. Implement via parentId: root comments have parentId: null, responses reference parent. Don't nest deeper than one level in mobile UI — inconvenient.
Thread component opens as bottom sheet (iOS: UISheetPresentationController with .medium and .large detents; Flutter: DraggableScrollableSheet). Doesn't cover entire screen, keeps pin context.
What's Involved
- Pin component with normalized coordinates and zoom support
- Comment addition and editing form
- Response thread in bottom sheet
- "Resolved" status with visual distinction
- REST API integration + optional WebSocket sync
- Pin clustering with large quantities
- Support for images, PDF, arbitrary View containers
Timeline
Basic implementation (pins on image, no thread or sync): 2 days. Full version with threads, real-time sync and clustering: 4–5 days. Cost calculated individually after analyzing requirements and existing API.







