I rebuilt iMessage line for line. Nobody wanted it.

I spent about a year rebuilding iMessage in SwiftUI. Not a chat UI similar to iMessage, but the real deal. Everything. The collated bubbles that only have a tail at the end. The typing dots that breathe in a wave. The keyboard you can drag down with your finger from the top of the input bar. The “Delivered” fading into “Read” smoothly with the same scale effects. I am still impressed that I was able to do it. It might be the best UI I’ve ever made.1

Then I turned it into a package, and nobody wanted it.

Here it is, in all its glory:

This is not iMessage. This is my package. You can literally embed this in your app now. Watch the message animate out of the input bar. Watch “Delivered” turn into “Read.” Watch the typing dots. Look at the collated messages. It’s pretty cool.

What went into it

I sort of always had admired iMessage, but the impetus to rebuild it came from a client. Protector wanted a chat-with-your-protector feature in their app, and Nick (the CEO) was shocked that Apple didn’t have an iMessage SDK. Later, Polymarket wanted a chat in its markets. So I was seeing the pattern. I decided to just build it once, for everyone.

Reading the build post, you can see the effort that went into it. Here’s the log, from the private source repo:

Commits655
Days with a commit85
First commitAugust 11, 2025
Most recent commitSeptember 7, 2026
Lines written36,755
Lines deleted17,372
Swift lines standing17,286
ChatViewController.swift3,845 lines
Commits mentioning the bubble117
Commits mentioning the tail35

Even though this is packaged as an easy SwiftUI view for anyone to use, it obviously uses UIKit under the hood. A UICollectionView powers most of it, because List is useless. The one collection view file is almost a quarter of the package. The keyboard drag has to be corrected frame by frame. Every bubble is attached to a stiff mini spring. The bubble shape was hand-traced from an iMessage screenshot. The new-message haptic is a custom buzz pattern that I had to generate to match iMessage’s feel. Here’s the result of two of those things:

It’s very important that the drag begins from the top of the input bar, not the top of the keyboard. The messages come down too, because they’re attached to a scroll view. None of this is possible with SwiftUI.
Hard flicks in both directions on a short thread. If you look closely, you can see the bubbles overshooting their destination and then settling as a chain of springs. Each cell lags behind its neighbor a few pixels instead of just moving as a solid sheet.

By the way, twelve people have commits in the repo. I made about sixty percent. The tail shape that was finally perfect came from a contractor I hired last year.

Over and over in the process, people told me this stuff wasn’t necessary. But somehow I just felt compelled to build it. I wanted exactly the same thing Apple had built in my apps. And I basically ended up getting there.

The big day

I polished the README and made the screenshots in both light and dark mode. I built an entire separate demo app so people could try it.2

The union-chat README on GitHub: the UnionChat title, the line SwiftUI-native iMessage replica SDK, one phone screenshot of the demo conversation, and the six feature bullets
The README on GitHub, which is what people landing on the package would see. It’s still mostly correct.

Then I pushed the package to the public, wrote about it here on this blog, and I think I tweeted a few times. I didn’t do much more than that. I was scared to, honestly.

Nothing happened.

To be clear, this was barely a launch. So I don’t want to overstate it. But I was still surprised by the silence. Here’s the public repository today, ten months later:

Stars9
Forks0
Watchers0
Issues ever opened0
Views, last 14 days20 (5 unique)
Clones, last 14 days12 (7 unique)
Top referrergithub.com

Twelve clones over the course of two weeks, mostly me I think. I thought maybe something was broken.3

Comparing my other packages

I could say it was just that nobody wants Swift packages. But I have a bunch of them, published on my same org, and promoted in a similar way. So you can compare them:

Effortlines of Swift
union-chat17,231
union-toast2,945
union-buttons2,064
union-tab-view1,055
union-confetti435
ReceptionGitHub stars
union-chat9
union-toast50
union-buttons4
union-tab-view128
union-confetti25
union-chat1,915 lines per star
Every Swift package I’ve published under Union St, measured today. Lines of Swift counted from the shipping source, excluding tests. Stars from the GitHub API. union-chat is almost six times the code of the next biggest repo and has the second-fewest stars. union-tab-view, a tab bar, went up eleven days after union-chat and it has fourteen times the stars.

union-tab-view is a tab bar. It went live eleven days after union-chat. It’s one sixteenth the code. And it has fourteen times the stars. union-confetti is 435 lines of code that just shows confetti, and it beat union-chat almost three to one.

So it wasn’t that people didn’t want Swift packages. They just didn’t want this one.

The diagnosis

My first theory was distribution. Just that I posted on the wrong day. Or the wrong channel, or needed a better launch video. The last package I had launched became the most upvoted post in the history of r/swift, so this was the theory: the product was great but the launch just wasn’t loud enough. And we could fix that.

It took me a while to admit to myself that a real launch would have barely changed anything. You cannot market a product that nobody wants or asked for. And nobody wanted this.

Here’s the calculation I should have done before I made this. How many people are making a native iOS app with a chat screen this year? Definitely some. How many want their chat to look exactly like iMessage instead of their own branding? Fewer, but still some. How many of those people would want to adopt a dependency instead of building it themselves? Even fewer. And how many of those very, very few remaining would find my package?

I think I was a bit afraid of the answer to that. The two clients who kicked off this exploration had wanted chat screens. The line-for-line recreation was really just my objective. Nobody else wants to waste time making sure their typing indicator breathes right. I was chasing a standard more than anything. Something I held up for myself.

The responses people did give me were all basically “wow, impressive.” Nobody was saying “finally.” And “impressive” but “no” times a thousand is zero usage.

That’s what I really was blind to. Building something hard is not the same as building something people want. And you only get users when people want what you built.

The last thing working against it might have been that it shipped as a closed-source binary. Nobody actually ever complained about that, but I was waiting to open the source until someone asked. The final thing that shipped was basically some Swift around the xcframework. So you can’t read it or patch it. Also, it requires iOS 18 and uses a third-party image library. Even after forty-six releases it’s still not out of beta. So the very few people who did stumble across the package had good reasons to pass on it. It really was built for me, I guess.

The coda

Here’s something interesting:

0100200Aug2025SepOctNovDecJan2026FebMarAprMayJunJulAugSeppublishedRate.fm ships it655 commits over 14 months, 6 of them silent
Every commit in the union-chat source repository, by month. The package went public on November 17, 2025 at the top of the spike. It decays slowly through March and then is at zero through June. But it comes back in the summer. That’s not because it got traction. It’s because Rate.fm needed a chat screen.

Two hundred and eighty-one commits in the month I launched. Then there was a long decay before not being touched in March, April, May, and June. But it came back in a big way this summer.

Not because people picked it up.

A few weeks after launch, union-chat was added to Rate.fm, the music app I actually run. I originally used it to leave comments under reviews. Now, it’s a fully-featured chat product with real users. So every detail I churned out for the past year, collated tails, the delivery labels, the interactive keyboard, is now in an app that hundreds of people are using every day.

Rate.fm, on my phone this week. A press and hold on a message, with the tapback bar unfurling on real glass. Then a swipe to the second reaction page and a final skull emoji reaction. You can even see how the bubbles move to make room for the reaction. union-chat lets you choose the foreground style of the sender’s bubbles.

And Rate.fm isn’t the only one! Polymarket still runs a heavily modified version of union-chat. It’s also in Tally, my calorie counter and workout app, as the coach chat, and in Jester, where you can paste in a screenshot of a conversation and ask what to say next:

Tally and Jester, running on my phone. Both use the package with some built-in color customizations. In Tally, the coach replies with a food card inside a bubble.

So the code is in four real apps now, and the forcing function from the users has made the package much better much more quickly.

I don’t really regret it. The work definitely forced me to be better in a way clients never did. No client would ever pay for thirty-five message bubble tail commits.4 Everything I did in the package went into craft skills that I used elsewhere in my apps. And now it’s embedded in products I own.

I don’t really have any reason to open the source, because nobody has asked. There might be too much now. I’m not sure. Open to recommendations! But the package for now is free, closed, and unadopted, while it runs in four real apps. And maybe more from me to come.

So nobody wanted it. And then eventually someone did. Me. Not a very good business plan. But not the worst ending.

Footnotes

  1. I’m a bit biased, but the receipts are in this post: springs, frame-by-frame keyboard math, and the hand-traced bubbles.

  2. The demo app also has no users :)

  3. In the first draft of this post I rhetorically said “the ninth version of the bubble tail.” The real commit log shows thirty-five commits mentioning the bubble tail. They started a week before launch and were most recently last week. So ten months on the bubble’s corner.