Introducing Clip- 15 mins
One year ago, I was preparing for the upcoming launch of AltStore — my attempt at building an alternative App Store for non-jailbroken devices (maybe you’ve heard of it). I originally built AltStore as a way to distribute Delta, but soon pivoted towards a more general distribution platform for all sideloadable apps. However, if I was going to launch a whole app store and frame it as such, surely there needs to be more than just one app in there, right?
Well, that’s where Clip comes in!
Clip is a clipboard manager for iOS that can run in the background indefinitely, listening for changes to the clipboard and saving your clippings for later use. Normally this would be impossible without jailbreaking, but Clip uses several workarounds to achieve this functionality within the constraints imposed by iOS. Unfortunately, despite working just fine these workarounds are all against App Store rules…which is what makes Clip perfect for AltStore 🎉
Clip’s main (and only) screen, displaying all your recent clippings.
For this initial version, I’ve really focused on the core experience of Clip running in the background and saving all clippings. This means Clip most likely does not have the breadth of features you’d find in an App Store clipboard manager, but what it does do, no App Store app can match (yet?).
- Monitor clipboard even when Clip is in background.
- Save text, URLs, and images copied to the clipboard.
- Copy, delete, and share clippings.
- Customizable history limit.
If you’ve been wanting a true clipboard manager for iOS as much as I have, give Clip a try! Just like Delta, you can download Clip directly from the “Browse” tab of AltStore. As with all my AltStore apps, the complete Clip source code is available on GitHub, so you’re also welcome to compile it yourself with Xcode if you’d prefer.
…but wait, if I didn’t want to launch AltStore with just one app, why did I wait till now to launch Clip? Well about that…turns out, getting to this point was much more effort than I ever expected 😅 So if you’re interested in the many challenges I had to overcome while developing Clip, the rest of this blog post is for you 👌
Challenge 1: Run Clip in Background Indefinitely
Due to iOS restrictions on what apps are allowed to do, there were two crucial pieces of functionality I needed to find workarounds for in order for Clip to be a “real” clipboard manager:
- Run in background indefinitely.
- Access clipboard while in background.
The first problem dates all the way back to iOS 4 when multitasking was first introduced. There is simply no official way for apps to run indefinitely in the background; when you leave an app, it is suspended in the background to save energy. For Clip to work though, it needs to continuously listen for clipboard changes, which (unsurprisingly) doesn’t work when suspended.
As it turns out, I’m not the first person to try and make an iOS clipboard manager that can run in the background. My favorite Mac clipboard manager Pastebot actually began life as an iOS app, and over ten years ago (!!) its developers came up with a creative workaround for this problem. As explained on the Tapbots blog:
About a week later (after iOS4’s release), Paul came up with an idea that would actually allow Pastebot to run in the background. Yes, you heard that right. It was a bit of a gamble, but it did not use any private API’s. All we want is to make Pastebot work in the way we always intended it to. The idea is to have a silent audio clip play in the background. This would use one of Apple’s backgrounding APIs that allows music to play in the background. Turns out it works flawlessly. You can finally copy from your Mac or on your iPhone without having to open Pastebot after each copy.
Unfortunately — but not unexpectedly — Apple rejected that Pastebot update, so this workaround never actually shipped with Pastebot. I couldn’t imagine why this method wouldn’t still work today though, so I decided to try it out.
First steps first, I added the
UIBackgroundMode to Clip’s
Info.plist to prevent iOS from suspending Clip as long as we’re playing audio. Using a silent audio file I made with Garageband, I wrote some code to “play”
Silence.m4a on loop, and also present local notifications every second (so I could track Clip running in the background). I then installed Clip to my phone, launched it without Xcode attached, and returned to the home screen. A notification appeared — but I kept waiting since I knew iOS apps could run for a few seconds before getting suspended. 5 seconds passed…then 10 seconds….then 30….and notifications kept appearing each second. I happily let it run for another several minutes, but it soon became apparent this would work after all! 🎉
One challenge solved, one more to go.
Challenge 2: Access Clipboard While Backgrounded
On iOS, you access the contents of the clipboard via the
UIPasteboard API. As of iOS 9, however, calling this API from the background will fail, preventing random apps from snooping on your clipboard from the background. Obviously this was a big problem for Clip, so I had to somewhat think outside the box to come up with a solution.
I hypothesized that iOS might not count app extensions as being “backgrounded” — even if their host app is — so I started experimenting with different app extensions. As luck would have it, from trial & error I discovered this background restriction did not apply to Audio Unit extensions. This was huge! What exactly are Audio Unit extensions though? They’re basically audio effect plug-ins that other apps can use to manipulate audio in real time. Normally they’re loaded by other apps besides their parent app — such as when GarageBand loads 3rd party effects — but there’s no reason the parent app can’t also launch the extension itself. I moved all clipboard tracking logic into a separate Audio Unit extension, and since I was already playing silent audio to run in the background via
AVAudioEngine, I piped that audio stream through the Audio Unit extension to make sure it was always running alongside Clip. Clip could finally monitor the clipboard while running indefinitely in the background, which meant it was time to polish up a 1.0.
Some of the available app extensions in Xcode.
A month later Clip 1.0 was more-or-less ready for launch, so I paused development to finish working on AltStore. At this point, I had two distinct apps ready for AltStore’s launch — Delta and Clip — so I planned all launch material around this: AltStore wasn’t just a way to download emulators, it was a way to download all sorts of apps not allowed in the App Store! Clip was central to this message, and by including it in AltStore at launch I hoped to solidify that.
Challenge 3: Access Clipboard While Backgrounded on iOS 13
Unfortunately…things did not go as planned. The night before I released AltStore, I ran Clip through some last minute testing…and discovered Audio Unit extensions could no longer access the clipboard in the background on iOS 13! iOS 13 had been released earlier that week, but I had been so busy preparing AltStore and Delta for launch I had forgotten to test Clip on iOS 13 up until that point 🤦♂️ Not to mention, I discovered this at around 3am during my final rounds of testing, so I just didn’t have time to look into it more. Reluctantly, I ended up cancelling Clip’s launch, despite already writing about Clip in my AltStore blog post 🙁
Once things had calmed down a bit post-launch, I looked into Clip more. I found that it wasn’t just Audio Unit extensions that could no longer access the clipboard in the background; all app extensions were now forbidden. This was…not the outcome I was hoping for, and was stumped for a while on how to proceed. That is, until one day a thought occurred to me: app extensions can’t access the clipboard in the background, but what about if they’re in the foreground? If there was some app extension that could run in the foreground on-demand in order to retrieve the clipboard, that might just work…right? I quickly clicked “Add Target” in the Clip Xcode project and reviewed all available app extensions, looking for one that might be usable. Most I could dismiss immediately (such as anything without a UI, or anything that can’t be initiated programmatically), but one caught my attention the moment I saw it: Notification Content extension.
Notification Content extensions are run whenever the user opens a notification with an identifier supported by the extension. Most importantly, they can display a fully custom, dynamic UI inside the notification itself. The only question was: would iOS consider this to be running in the “foreground”? Only one way to find out! I added a Notification Content extension target, added a simple
print(UIPasteboard.general.string) to print the contents of the clipboard when the extension launches, then had Clip repeatedly schedule local notifications. I ran Clip on my device, returned to the home screen, then waited for a local notification to appear. Once one did, I pulled it down to open it and…my iPhone’s clipboard contents were printed in Xcode’s console! It worked! 🥳
Notification Content app extension in action.
From here, I spent the next week or so shaping this proof-of-concept into a shippable product. Since I had already planned to ship Clip with the Audio Unit extension workaround, it didn’t take long once I had refined this new workaround before Clip was once again in a shippable state. I (re-)announced Clip on Twitter along with the first downloadable beta (available to my patrons). I continued to beta test Clip for the next couple months, finishing up what I once again thought would be Clip 1.0. The Notification Content extension appeared to be working well, and I soon released what I considered to be the Clip 1.0 GM build to beta testers. In the release notes for that version, I even mentioned the public release was imminent! I was so sure the time had finally come to release Clip, and I was ready.
Challenge 4: iOS 13.3.1 Breaks Sideloaded App Extensions
Naturally, that’s when my iOS update woes began. On January 28 — shortly after I released Clip 1.0 GM to testers — Apple released iOS 13.3.1, a seemingly minor update by all appearances except for one thing: all apps installed with AltStore crashed on launch 😅 Long (but frantic) story short, it turned out for whatever reason iOS 13.3.1 refused to launch any apps signed with free developer accounts (aka, AltStore apps and any apps using Xcode without a paid developer account) that contained dynamic frameworks or app extensions.
The dynamic framework restriction was annoying but not impossible to work around, so I managed to release updated versions of AltStore and Delta converted to entirely use static libraries vs dynamic frameworks within a couple weeks (just like the good ol’ days of iOS development!). As for the app extension restriction…Clip’s main functionality relied on an app extension, so it seemed it was impossible for Clip to work, and I reluctantly paused development (again) to focus on AltStore and Delta.
Thankfully, this issue ended up resolving itself. iOS 13.4 was released a couple months later, which once again allowed sideloaded apps to use frameworks and app extensions. This didn’t matter too much for AltStore and Delta since I had already successfully migrated to static libraries (which deserves its own blog post…), but it meant other apps people were sideloading with AltStore started working again too (since almost every app uses frameworks nowadays). Additionally, now that app extensions were supported again, it would be possible to release Clip!
Challenge 5: App Extensions Count Towards Sideloaded App Limit
Except…with 13.4 came another big caveat (of course). While installing and running sideloaded apps with app extensions worked fine, app extensions were now included in the 3 sideloaded apps limit enforced by iOS. In other words, every sideloaded app extension now counted as a sideloaded app, meaning an app containing just 1 app extension would take up 2 sideloaded app slots. Since AltStore itself required 1 slot, this meant apps containing more than 1 app extension could not be installed by AltStore anymore. More importantly, however, any apps containing more than 2 app extensions could not be sideloaded period, even with Xcode (unless you are a paid developer).
As with most things Apple, it was hard to guess whether this change was intentional, or was simply a bug introduced simultaneously with the 13.3.1 dynamic frameworks bug. I was cautiously optimistic it was the second, and hoped to see it fixed soon. Regardless, there was little I could do on my end to work around it, so I had to accept this new restriction (even if it ended up being temporary) and adjusted my AltStore and Delta roadmaps to de-prioritize any features relying on app extensions (such as Siri Shortcuts support for AltStore 💔).
sigh Apple giveth, Apple taketh away.
Challenge 6: Actually Launching Clip
For a few weeks, there was some stability: neither iOS 13.4.1 nor the 13.4.5 betas drastically changed how the system treated sideloaded apps. App extensions still counted towards the sideloaded app total, but honestly after the previous two updates I considered no more surprise restrictions/bugs a win. This streak continued until Apple released “iOS 13.5 beta 3”, a renaming of iOS 13.4.5 which included the first version of
ExposureNotification.framework, Apple’s COVID-19 contact tracing framework.
Thankfully, that wasn’t the only change in 13.5. As usual, I downloaded the beta to see if there were any changes for sideloaded apps…and to my delight I discovered app extensions no longer counted towards the sideloaded app limit 🥳 For the first time in almost 6 months, it seemed like I would finally be able to release Clip after all. Once 13.5 was released in late May and I confirmed the fix was still present, I once again set to work preparing Clip for release.
…and that’s where we are today! Phew! Almost 9 months after it was initially supposed to launch, Clip is now available for everyone to download through AltStore. I hope Clip will serve as a valuable tool for anyone looking for a simple iOS clipboard manager, and I would love to hear all your feedback on how I can improve Clip in the future 💜
Clip’s product page in AltStore.
But on that note, there is one last thing (I promise) about future features…
Feature Preview: Custom Keyboard
Beyond the core features in Clip 1.0, there’s one feature in particular I’ve been particularly excited to work on: a keyboard extension for quick access to clippings. While preparing this launch I spent some time prototyping such a keyboard, but quickly got carried away and ended up polishing it into a “real” feature. For that reason, I’m also happy to announce Clip 1.1, the first major update to Clip that will come with this new custom keyboard:
Light & dark versions of the Clip keyboard with the standard Mail toolbar.
Clip 1.1 will be a free update to Clip once finished, and a beta version is available now for all my patrons via AltStore. As a reminder, I will be donating all my Patreon earnings for the month of June to causes supporting Black Lives Matter, so if you’re interested in beta testing Clip’s custom keyboard (or any other beta versions of my apps) while also supporting a great cause, feel free to donate to my Patreon here 🙂💜