DEV Community

Cover image for I Built a Wallspace App in Swift, Got 22,000 Users in 3 Months, and Learned More Than I Expected
Roman May
Roman May

Posted on

I Built a Wallspace App in Swift, Got 22,000 Users in 3 Months, and Learned More Than I Expected

A few months ago I shipped the first version of Wallspace — a native Swift app that brings cinematic live wallpapers to the Mac desktop and lock screen.

It was broken in a dozen ways. Buttons that did nothing. Features half-implemented. Performance that embarrassed me.

Today it has 22,000 users, 95,000+ wallpaper downloads, and 800+ Discord members. Still solo. Still shipping.

Here's everything I learned.


Why I Built It

I wanted Wallpaper Engine on Mac. If you've used Windows you know what I mean — live wallpapers running in the background, beautiful, resource-efficient, just there.

Mac didn't have a good equivalent. The options were either:

  • Electron-based apps pushing 15% CPU to loop a video
  • App Store apps with watermarks on every wallpaper
  • Subscription services charging monthly for something you set and forget

So I decided to build the thing I wanted to use.

The constraints I set for myself from day one:

- Native Swift only. No Electron, no web wrapper.
- Under 2% CPU in typical use
- Free to start, one-time Pro upgrade
- No login required. Ever.
- Lock screen support on macOS 26
Enter fullscreen mode Exit fullscreen mode

The Technical Decisions That Mattered

AVFoundation over everything else

The single most important technical decision was using AVFoundation with hardware-accelerated decoding instead of a software video player.

Most wallpaper apps decode video in software. That means the CPU handles every frame, continuously, in the background. Even on Apple Silicon that adds up — especially on MacBook where thermal throttling kicks in under sustained load.

AVFoundation delegates decoding to the media engine and GPU. The CPU barely participates. That's how Wallspace stays under 2% while playing a 4K video loop.

let playerItem = AVPlayerItem(url: wallpaperURL)
let player = AVQueuePlayer(playerItem: playerItem)
let looper = AVPlayerLooper(player: player, 
                             templateItem: playerItem)
Enter fullscreen mode Exit fullscreen mode

Simple in principle. The complexity is in the edge cases — display sleep, spaces switching, full-screen apps, battery state changes. All of these need to trigger intelligent pause/resume behaviour or you burn CPU invisibly.

Rendering into the desktop layer

Getting video to render behind app windows but above the desktop background required digging into private-ish NSWindow behaviours. The window level matters enormously:

window.level = NSWindow.Level(
    rawValue: Int(CGWindowLevelForKey(.desktopWindow))
)
window.collectionBehavior = [
    .canJoinAllSpaces,
    .stationary,
    .ignoresCycle
]
Enter fullscreen mode Exit fullscreen mode

Getting this wrong means your wallpaper either disappears behind the desktop or floats above your apps. Neither is acceptable.

Lock screen support

This was the hardest feature to ship. Lock screen wallpaper on macOS isn't a public API — Apple doesn't expose it cleanly. Getting Wallspace to set a live wallpaper at the system level required reverse-engineering how macOS 26 handles the lock screen layer and finding a stable, non-private-API path to set it.

It took longer than the rest of the app combined. But it's the feature users ask about most.

Multi-display handling

Each display needs its own AVPlayer instance, its own window, its own memory budget. When a display is disconnected, you clean up immediately. When one is added, you instantiate and apply the current wallpaper within a frame or two.

NotificationCenter.default.addObserver(
    self,
    selector: #selector(displaysChanged),
    name: NSApplication.didChangeScreenParametersNotification,
    object: nil
)
Enter fullscreen mode Exit fullscreen mode

Sounds simple. The edge cases — display sleep vs disconnect, mirroring mode, Sidecar — took weeks to get right.


The Growth Story

Month 1 — Discord and close feedback

First users were friends and a small Discord server. The app was rough. I shipped updates almost daily based on direct feedback. No analytics, no crash reporting beyond what users told me — just Discord messages saying "this broke."

The early adopter feedback loop is underrated. Those first 50 users told me everything that mattered.

The viral moment

One tweet from a user with a decent following hit around 80k impressions overnight. Downloads jumped from a few hundred to over 1,000 in 24 hours.

Nothing about that was engineered. It just happened. But it taught me that the product had to be ready for that moment — because you don't get a second first impression.

Month 2–3 — SEO and word of mouth

After the spike, growth settled into a steadier pattern driven by:

  • Blog posts targeting keywords like "live wallpaper for mac" and "wallpaper engine alternative mac"
  • Reddit posts in r/macapps, r/macgaming, r/MacOS
  • Organic word of mouth in Discord communities and Twitter/X

No paid ads. No Product Hunt launch yet. Just SEO and community.

Current numbers:

Users:              22,000+
Wallpaper downloads: 95,000+
Discord members:     800+
Time to get here:    ~3 months
Marketing budget:    ~$0
Enter fullscreen mode Exit fullscreen mode

What I Got Wrong

Underestimated edge cases

Every macOS feature that seems simple has a long tail of edge cases. Display management, sleep/wake cycles, Spaces behaviour, full-screen apps — each one took longer than I expected.

Shipped too late on lock screen

Lock screen support was the most-requested feature from day one. I deprioritised it because it was technically hard. I should have tackled it earlier — it would have accelerated growth by months.

No structured onboarding

The app works immediately with no setup. That's intentional. But I had no in-app guidance, no empty state design, no first-run experience. Users who didn't immediately "get it" just left. I've since improved this but lost early retention I shouldn't have.

Ignored SEO for too long

I treated the website as a download page for the first two months. Basic SEO work — title tags, meta descriptions, structured data, blog posts — should have started on day one. It's compounding now but I left months of organic growth on the table.


What Actually Worked

Building in public, even quietly

Discord updates, Twitter posts about technical challenges, honest progress sharing — these built a small but genuinely invested community who became the best word-of-mouth channel I have.

Solving a real problem I had

I use Wallspace every day. That sounds obvious but it matters. Every decision about performance, UI, and features was made by someone who actually cared about the outcome. Users can tell.

Keeping the free tier genuinely free, checkout the pricing

Not a trial. Not feature-crippled. Genuinely free with a meaningful but optional Pro upgrade. This lowered the barrier to trying it to near zero and let the product speak for itself.

Native Swift

The performance advantage over Electron-based competitors is real and measurable. Users notice. It shows up in reviews, tweets, and Discord messages constantly. "This is the first wallpaper app that doesn't drain my battery" is the most common piece of feedback I get.


What's Next

  • Individual wallpaper landing pages for SEO
  • Structured data and schema markup
  • AI-powered wallpaper search and recommendations
  • Product Hunt launch
  • Expanding the wallpaper library

Try It

If you're on a Mac and curious — Wallspace.app. Free to download, no login, works in 30 seconds.

And if you're building something similar or have questions about any of the technical decisions above — happy to discuss in the comments. This community taught me a lot of what I know. Happy to give some of it back.


Built with Swift, AVFoundation, and an embarrassing number of late nights.


This format works well on dev.to because it leads with the technical credibility, tells an honest story including failures, and gives developers enough substance to actually learn something — which is what gets posts featured and shared on that platform. The code snippets signal authenticity without overwhelming the narrative.

Top comments (0)