NutriKit · iOS 26 · SwiftUI

Shareable Glass Stickers

How we render real liquid glass effects on meal stickers you can share as images or videos

See How It Works
642kcal
The Idea

What are glass stickers?

NutriKit lets you share your meals as beautiful stickers — nutrition overlays that sit on top of your own photos or videos. The sticker looks like a piece of frosted glass: you can see the background blurred through it, light catches the edges, and the whole thing feels physical.

1

Pick your photo or video

Choose a background from your library. The sticker appears on top with real glass effects — drag it around, pinch to resize, tap to cycle through variants.

2

iOS 26 does the glass

We use Apple's native .glassEffect() — the same system that renders glass on tab bars and buttons. The glass blurs, refracts, and lights up based on what's behind it.

3

The hard part: capturing it

Glass effects live in Apple's compositor — they can't be exported with ImageRenderer. We capture them by drawing the actual window hierarchy, which gives us the real composited pixels.

4

For video: every single frame

Video export renders each frame one by one — decode a frame, update the background, capture the glass compositor, write to the output. At 30fps for a 10-second video, that's 300 renders.

Three export modes: Transparent PNG sticker (no background), image composite (sticker + photo), and frame-by-frame video (sticker + video with audio). Each one has a fundamentally different rendering pipeline.

Export Modes

Three ways to share

Each mode solves a different problem — from simple SwiftUI rendering to full compositor capture to frame-by-frame video encoding.

1

Plain Sticker

PNG

No background — just the nutrition sticker as a transparent PNG. Uses SwiftUI's ImageRenderer directly.

SwiftUI ViewImageRenderer.png
2

Image + Sticker

JPEG

Glass sticker composited onto a photo. Captured from the live screen via drawHierarchy.

Screen CaptureCrop 9:16Composite.jpg
3

Video + Sticker

MP4

Every frame decoded, composited with glass in an offscreen window, captured, and re-encoded.

DecodeGlass RenderCaptureEncodeMux Audio
Glass System

The GlassBackground modifier

Every sticker variant wraps its content in a .glassBackground() modifier that adapts based on three environment flags — choosing between native liquid glass, semi-transparent fallbacks, or transparent mode for video compositing.

transparentMode = true

Content clipped to shape only. No background. Used during video export.

.clipShape(shape)
useGlassEffect = true

iOS 26 native liquid glass. Blur, refraction, and specular highlights automatic.

.glassEffect(.clear, in: ...)
Dark mode fallback

Layered semi-transparent backgrounds with gradient border stroke.

black + white + gradient stroke
Light mode fallback

Semi-transparent white background with thin black border.

white(0.75–0.95) + stroke
Image Pipeline

Photo export — one-shot capture

When the user shares a sticker on a photo, we need to composite the glass effect at full resolution. The trick: capture the glass compositor from the actual screen, then blend it onto the high-res background.

1

Crop the background

Calculate the visible crop region in original pixel coordinates — accounting for display scale and user panning.

let pixelScale = imgPixelW / displayed.width
let visibleOriginX = (displayed.width - frameSize.width) / 2
                   - imagePanOffset.width
let cropRect = CGRect(
    x: visibleOriginX * pixelScale,
    y: visibleOriginY * pixelScale,
    width: frameSize.width * pixelScale,
    height: frameSize.height * pixelScale
)
CGImage
2

Capture the glass from screen

ImageRenderer can't capture glass effects — they live in Apple's compositor. Instead, we capture the actual key window via drawHierarchy.

let window = windowScene.windows
    .first(where: { $0.isKeyWindow })

let fullScreenImage = renderer.image { _ in
    window.drawHierarchy(
        in: window.bounds,
        afterScreenUpdates: true
    )
}
Why drawHierarchy? It's the only public API that captures compositor-level effects — backdrop filters, glass, and vibrancy.
UIWindowdrawHierarchy
3

Composite at full resolution

Two images layered: full-res cropped background and screen capture with glass. The blur makes the upscale imperceptible.

let finalImage = renderer.image { _ in
    UIImage(cgImage: croppedBgCG).draw(in: fullRect)
    UIImage(cgImage: screenCropCG).draw(in: fullRect)
}
UIGraphicsImageRenderer
4

Export as JPEG

Compressed to JPEG at 95% quality, saved to a temp file, presented via the system share sheet.

JPEG 95%UIActivityViewController
1
drawHierarchy call
Full-res
Background quality
9:16
Story aspect ratio
Video Pipeline

Video export — frame by frame

Video is where things get wild. We can't just overlay a static sticker — the glass effect must update every frame because the background changes. So we decode each frame, render it into an offscreen window with the glass sticker, capture the compositor output, and encode it to a new video.

1

Build the offscreen render window

A hidden UIWindow at level -1000. Inside: a UIImageView for video frames, and a UIHostingController with .glassEffect.

let window = UIWindow(windowScene: windowScene)
window.windowLevel = UIWindow.Level(rawValue: -1000)
window.isHidden = false  // must be visible to compositor

let stickerContent = variant.stickerView(for: meal)
    .environment(\.stickerUseGlassEffect, true)
let hostingVC = UIHostingController(rootView: stickerContent)
Why a real UIWindow? Glass effects require the compositor. The window must be isHidden = false — we just push it behind everything else.
UIWindowUIHostingController
2

Decode video frames

Each frame via AVAssetReader as 32BGRA. We handle orientation via CIImage.oriented(), then crop to the 9:16 export region.

var frameCI = CIImage(cvPixelBuffer: pixelBuffer)
frameCI = frameCI.oriented(videoOrientation)
frameCI = frameCI
    .cropped(to: cropRect)
    .transformed(by: CGAffineTransform(
        translationX: -cropRect.origin.x,
        y: -cropRect.origin.y
    ))
AVAssetReaderCIImage
3

Two-pass glass capture

The heart of the video pipeline. For each frame, a two-pass drawHierarchy:

  • Pass 1: Force the compositor to process the new background
  • Pass 2: Capture the output with glass now reflecting the current frame
backgroundImageView.image = frameUIImage
renderWindow.rootViewController?.view.layoutIfNeeded()

// Pass 1: force compositor update
_ = renderer.image { _ in
    renderWindow.drawHierarchy(
        in: renderWindow.bounds, afterScreenUpdates: true)
}

// Pass 2: capture with glass reflecting current frame
let captured = renderer.image { _ in
    renderWindow.drawHierarchy(
        in: renderWindow.bounds, afterScreenUpdates: true)
}
Why two passes? The glass compositor is async. The first pass forces layout; the second captures the fully-updated output.
drawHierarchy ×2Compositor
4

Encode to HEVC

Captured bitmap → CVPixelBuffer via CIContext.render()AVAssetWriter. HEVC-encoded at export frame size × screen scale.

AVAssetWriterHEVC
5

Mux audio back in

Original audio re-attached via AVMutableComposition + AVAssetExportSession with passthrough — no re-encoding.

AVMutableCompositionPassthrough
6

Clean up and share

Offscreen window deallocated. Intermediate file deleted. Final MP4 presented via share sheet with progress bar throughout.

UIActivityViewController
drawHierarchy per frame
HEVC
Video codec
No re-encode
Audio mux
Architecture

Full pipeline diagrams

Image Export

User's PhotoUIImage
Crop to 9:16CGImage.cropping()
Full-Res Background
+
Live ScreenKey Window
drawHierarchy
Crop 9:16previewGlobalFrame
Glass Overlay
Composite → JPEG 95%

Video Export

Source VideoAVAssetReader
Decode FrameCVPixelBuffer → CIImage
Orient + Crop.oriented() · .cropped()
UIImageViewin offscreen UIWindow
drawHierarchy × 2Glass compositor capture
CIContext.render→ CVPixelBuffer
AVAssetWriterHEVC encode
Mux AudioAVMutableComposition
Final .mp4

← repeat for every frame →

Side by Side

Image vs Video

Image Export

  • Single drawHierarchy call on live key window
  • Background cropped from original full-res image
  • Glass captured at screen resolution, composited over full-res
  • Output: JPEG at 95% quality
  • Near-instant — feels immediate
  • No offscreen window needed

Video Export

  • Two drawHierarchy calls per frame on offscreen window
  • Each frame decoded via AVAssetReader + CIImage
  • Offscreen UIWindow with real glass compositor
  • Output: HEVC-encoded MP4 with muxed audio
  • Progress bar — can take 10–30s
  • Cancellable via Task with cleanup
Key Insight

Why drawHierarchy is everything

The entire sticker export system is built around one limitation: glass effects can't be captured through SwiftUI's ImageRenderer. They only exist in the compositor.

ImageRenderer

Renders the SwiftUI view tree directly — but glass effects are compositor-level. You get a flat, un-glassed render.

drawHierarchy

Captures what the CALayer tree actually looks like on screen — including backdrop filters, glass, and blending.

The tradeoff

drawHierarchy is slow — it forces a full compositor pass. For video, hundreds of calls = progress bar.

The UIWindow trick

For video, we can't use the live screen. So we create a hidden window at level -1000 — invisible but composited.

Variants

5 sticker designs

Each variant uses the same glass system but shows different levels of detail. The user swipes through them in a carousel (no background) or taps to cycle (with background).

Minimal
642kcal
42g
68g
28g
App icon + calories + macro dots + distribution bar. Compact single row.
Standard
Lunch
12:30 PM
642 kcal
42g
Protein
68g
Carbs
28g
Fat
Header with meal name/time, calorie badge, macro columns with indicators.
Detailed
Lunch
642
Grilled Chicken
Brown Rice
Avocado
Full layout with food item list. Each item gets its own per-item macro bar.
Detailed + Emoji
🍗Grilled Chicken
🍚Brown Rice
🥑Avocado
Same as detailed but with food emoji icons next to each item.
Full Detail
🍗
Grilled Chicken, breast
🍚
Brown Rice, long grain
Everything — emoji, food detail text, brand names. The maximalist option.
Technology

The stack

SwiftUI Layer

.glassEffect(.clear)Native liquid glass
GlassBackgroundCustom ViewModifier
StickerPaletteGlass-aware colors
TextElementBackgroundInner element materials
Environment flagsopacity · glass · transparent

Capture + Export Layer

drawHierarchyCompositor capture
UIWindow (level -1000)Offscreen render target
AVAssetReader/WriterFrame decode/encode
CIContext + CIImageOrient, crop, render
AVMutableCompositionAudio muxing
UIHostingControllerSwiftUI → UIKit bridge

Try the glass stickers yourself

Get the Beta