●MAX — Rork Max generates native Swift for iPhone, iPad, Apple Watch, Apple TV, and Vision Pro, with 2-click App Store publishing and no Xcode required●STACK — Standard Rork builds cross-platform mobile apps with React Native (Expo); choosing between the two by use case is the key decision●FOCUS — Unlike web-first tools such as Bolt or Lovable, Rork specializes in native iOS and Android app generation●BUGS — A hands-on review reports Rork resolved about 70% of bugs without manual help, with the remaining 30% needing edits in the exported codebase●FUNDING — Rork raised $2.8M from a16z (Andreessen Horowitz)●PRICING — It is free to start, with paid plans from $25/month, so you can try before committing●MAX — Rork Max generates native Swift for iPhone, iPad, Apple Watch, Apple TV, and Vision Pro, with 2-click App Store publishing and no Xcode required●STACK — Standard Rork builds cross-platform mobile apps with React Native (Expo); choosing between the two by use case is the key decision●FOCUS — Unlike web-first tools such as Bolt or Lovable, Rork specializes in native iOS and Android app generation●BUGS — A hands-on review reports Rork resolved about 70% of bugs without manual help, with the remaining 30% needing edits in the exported codebase●FUNDING — Rork raised $2.8M from a16z (Andreessen Horowitz)●PRICING — It is free to start, with paid plans from $25/month, so you can try before committing
Building an iMessage Extension with Rork Max to Bring Your App's World into Messages — Notes on Distribution as Code
A walkthrough of adding an iMessage extension to the native Swift project Rork Max generates, sharing your app's assets right inside Messages. Covers compact/expanded presentation, sending messages, and diagnosing why the extension won't appear in the drawer — from an indie developer's distribution lens.
Run an indie wallpaper app long enough and you notice the moment users most naturally recommend it to a friend isn't when they send an App Store link — it's when they drop a favorite image straight into a conversation. If that's true, could I bring the app's world into Messages itself, the very place sharing happens? That question is what led me to build an iMessage extension.
An iMessage extension is a native mechanism that depends on the Messages framework. It's effectively out of reach from React Native, but because Rork Max generates a native Swift project, you can add an extension target and subclass MSMessagesAppViewController. Here I'll work through presentation switching, sending messages, and the pitfalls — all through the lens of distribution.
Why a Messages extension instead of the main app
Send an app pitch as a store link and the recipient has to leave the conversation to open the App Store. An asset sent from an iMessage extension, on the other hand, stays in the conversation, and the recipient can engage with it right there. In my experience, the psychological distance to "maybe I'll try this" is dramatically shorter with the latter. For a free, AdMob-centric app, adding one more entry point is far from trivial.
That said, the extension is a separate target from the main app, and its memory limit is stricter. Loading assets wholesale hits the ceiling fast. Designing for lightness is the starting point.
Step 1: Add the extension target and share assets with the main app
Add an iMessage extension target to the project Rork Max generated. To share assets (like images) with the main app, enable an App Group and read them through the shared container. Carrying images twice inside the extension bloats both the distribution size and memory.
// Resolve an asset URL from the shared containerfunc sharedAssetURL(_ name: String) -> URL? { let groupID = "group.net.rorklab.sample" let base = FileManager.default .containerURL(forSecurityApplicationGroupIdentifier: groupID) return base?.appendingPathComponent("assets/\(name)")}
The App Group identifier must match exactly between the main app and the extension. A mismatch leaves the asset always nil on the extension side, and you lose time tracking down the cause.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Concrete presentationStyle transition code for switching MSMessagesAppViewController between compact and expanded layouts
✦How to assemble an MSMessage that attaches an asset like a wallpaper, and design how it persists in the recipient's conversation
✦How to diagnose the production symptom of an extension never showing in the Messages drawer, down to target settings and bundle structure
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Step 2: Switch between compact and expanded presentation
An iMessage extension has two presentations: "compact," which fits at the keyboard position, and "expanded," which uses most of the screen. A natural flow is to show an asset list quickly in compact, then expand after selection to confirm the result.
import Messagesfinal class MessagesViewController: MSMessagesAppViewController { override func willBecomeActive(with conversation: MSConversation) { super.willBecomeActive(with: conversation) presentList() // Show the list first } func didSelectAsset(_ name: String) { // Once selected, expand and present the confirmation screen requestPresentationStyle(.expanded) presentDetail(for: name) }}
requestPresentationStyle(_:) is a request, not an instant switch. To rebuild the UI after the actual transition, combine it with didTransition(to:). Confuse the two and you render the old layout before the view expands, causing a brief flicker.
Step 3: Assemble the asset as a message and send it
The core of sharing is assembling MSMessage. The bubble that stays in the conversation is defined by MSMessageTemplateLayout. You design here what remains on the recipient's screen.
func send(asset name: String, in conversation: MSConversation) { let layout = MSMessageTemplateLayout() layout.image = UIImage(contentsOfFile: sharedAssetURL(name)?.path ?? "") layout.caption = "Sent you a favorite of mine" let message = MSMessage() message.layout = layout conversation.insert(message) { error in if let error = error { print("insert failed: \(error)") // Don't swallow insertion failures } }}
conversation.insert only "inserts the message into the input field" — actual sending is left to the user's send button. Mistake this for "sent" and you misread the by-design behavior (nothing is sent automatically) as a bug. That tripped me up at first, and it clicked once I understood insertion and sending are deliberately separate.
Step 4: Diagnose why it won't appear in the drawer
You built the extension and installed it on a device, yet it doesn't appear in the Messages app drawer — a frequent iMessage extension symptom. The cause is usually in configuration, not code.
The order to check is this. First, whether the extension target's Bundle Identifier correctly nests under the main app's identifier. Second, whether the extension's Info.plist sets the NSExtensionPointIdentifier for a Messages extension. Third, whether the app is enabled in the device's Messages settings. In my case, a mistaken extension point (the second item) left it building fine yet never appearing in the drawer.
Step 5: Keep the distribution effect measurable
You will inevitably want to know later how many new users the extension's sharing drove. Carry a URL with an identifying query in MSMessage.url, so the main app can measure the path when a recipient opens the app from there. I track these share-driven launches simply, alongside AdMob revenue. Once the numbers are visible, you can judge calmly whether the extension is worth the effort.
Deciding whether to invest in the extension as an indie developer
An iMessage extension is a domain you could polish endlessly. But viewed as a distribution channel, getting to "drop a favorite asset into a conversation in one tap" already delivers real effect. The approach I took as an indie developer was to concentrate on making one round-trip of sharing light and fast, leaving decorative features for later.
Messages is the place where users picture someone in their own words. Being able to quietly offer your app's world there carries meaning beyond promotion. Now that Rork Max puts a native extension within reach, whether you can think of distribution as something you design in code will, quietly, make a difference.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.