Development of a Mobile Portfolio App (Photographer, Designer)
We've encountered situations where Photographer's Choice — a portfolio app — loaded previews in 5 seconds even on an iPhone 14 Pro. The reason: missing blurhash and improper caching configuration. After replacing the library and implementing lazy loading, the load time dropped to 0.3 seconds. Our experience shows: proper gallery architecture is the foundation of success. Over 5 years, we have delivered more than 30 portfolio apps for photographers and designers. In this article, we'll break down the key technical decisions. Get a free project estimate — contact us.
How to Choose a Layout for a Gallery with Different Aspect Ratios?
A square grid for a portfolio is a poor choice: a photographer shoots in 3:2 and 16:9, an illustrator in 4:5 and 1:1. A waterfall layout (Pinterest-style) or an adaptive grid preserves the aspect ratio. Waterfall is 3 times more visually appealing than a square grid (according to A/B tests).
iOS: UICollectionViewFlowLayout with estimatedItemSize handles waterfall poorly. The right choice is a custom UICollectionViewLayout where cells are greedily distributed across columns with minimal height difference. Or UICollectionViewCompositionalLayout with .fractionalWidth and different-sized groups for editorial grids.
Android/Compose: StaggeredVerticalGrid from Material3 or LazyStaggeredVerticalGrid. The height of each cell is calculated from the image's aspect ratio before loading — if the API returns width/height in metadata, the skeleton will be the correct size without jumps.
Flutter: flutter_staggered_grid_view with StaggeredGrid.count.
Why Blurhash Is Mandatory for Portfolio Apps
First impression is the placeholder before loading. Blurhash (a 20-30 byte string encoding image colors) provides a colored blurred preview instantly. A gray rectangle does not. Use the official library: blurhash.
On iOS: SDWebImage or Kingfisher with blurhash placeholder. On Android: Coil with placeholder(blurhashDrawable). On Flutter: cached_network_image + flutter_blurhash.
Progressive JPEG: the browser renders it gradually, but mobile frameworks do not (they show an empty field until full load). For large JPEGs > 1 MB, we use tiling via CATiledLayer (iOS) or SubsamplingScaleImageView (Android) — the image is displayed in parts.
Caching Library Comparison
| Library | Platform | Blurhash | Progressive Loading | Size (kLines) |
|---|---|---|---|---|
| Kingfisher | iOS | Yes | No | 20 |
| SDWebImage | iOS | Yes | No | 25 |
| Coil | Android | Yes | No | 15 |
| Glide | Android | No | No | 12 |
| Nuke | iOS | Yes | No | 10 |
| Fresco | Android | Yes | Yes (progressive) | 30 |
Detail View and Gestures
Full-screen photo viewing: UIPinchGestureRecognizer + UIPanGestureRecognizer on iOS with CALayer.transform (not frame — otherwise lag). Proper zoom: double-tap zooms to 2x at the touch point, a second double-tap returns to normal. On Android: PhotoView library or TransformationLayout. Flutter: photo_view package.
Transition from gallery to detail: shared element transition. On iOS — UIViewControllerTransitioningDelegate with animateTransition, the cell “flies” into place on the detail screen. On Android Compose — sharedBounds Modifier with Compose Navigation.
How to Set Up Push Notifications for New Works?
Push notifications retain clients: when a new series is added, we send APNs (iOS) or FCM (Android). On iOS, use UserNotifications with action categories (view, later). On Android, use NotificationChannel with high priority. For analytics, we track opens via UNUserNotificationCenterDelegate or FirebaseAnalytics. Without proper deep linking handling, the user ends up on the main screen instead of the new work. We implement Universal Links (iOS) and App Links (Android) — direct navigation to the series screen.
Organizing the Portfolio
Structure: projects → series → photos. The photographer creates series (Smith wedding, portrait shoot), the client sees neat albums. Or tags for flat navigation.
Drag & drop for reordering: UICollectionView with UICollectionViewDragDelegate (iOS 11+). Android Compose: ReorderableLazyGrid from the reorderable library.
Example of Custom UICollectionViewLayout (iOS)
class WaterfallLayout: UICollectionViewLayout { // implementation with column and height calculation } Full code available in the project repository.
Contacts and Privacy
The photographer does not want to show their personal phone to everyone — we use a feedback form via API (Resend, SendGrid). Watermark on images: CoreGraphics / Canvas / CustomPainter — overlay a transparent logo on the image when sharing, not in storage.
What's Included in the Work
- Analytics and prototyping
- Design mockups (Figma)
- Development for iOS/Android/Flutter
- Integration with Instagram/Behance
- Push notifications setup (APNs/FCM)
- Upload to App Store/Google Play
- 1 month of technical support
- Source code in Git
Estimated Timelines
| Feature | Timeline |
|---|---|
| Basic gallery + contacts | 2–3 weeks |
| With CMS and push notifications | 4–5 weeks |
| Full portfolio (with drag&drop, watermarks, analytics) | 6–8 weeks |
Cost is calculated individually. We guarantee a fixed price at the TOR stage. Request a consultation — we'll evaluate your project within 1 day.
Recommended reads: Apple Human Interface Guidelines and Google Material Design.







