An incredibly nerdy deep-dive into the AirPower charging animation

Oct 22, 2018

Maybe this is not common for all developers, but as an iOS developer, one of my favorite things to implement are beautiful user interfaces and cute animations. When the UX people at my job presented me with a new onboarding screen for our app, it consisted of static screens on Zeplin, without any animations specified. I asked for the Sketch file and came up with this:

Needless to say, they were happy about the result and the product manager didn’t mind me spending a couple extra hours to do that. In case you’re wondering: I made the animations in Kite and exported each step to a separate Core Animation Archive file (a feature implemented in Kite per my request), created an UIView subclass to switch between the different animations and hooked up the swipe gesture to it so they would follow the user’s actions.

But I’m not here today to write about that project, I want to write about Apple’s ~seemingly dead~ project: AirPower.

Even before its announcement more than a year ago, while digging through a leaked build of the iOS 11 GM, I noticed that a new component of the system would be responsible for presenting a charging interface and that it would have some sort of 3D animation.

ChargingViewService confirms wireless charging. It will even show some 3D animations when charging (not present in GM tho) pic.twitter.com/JLBd4HQLpS

When Apple announced AirPower a few days later, the video they showed immediately caught my attention.

That was clearly the “3D animation” I was talking about. I knew there was going to be an animation because the component in question was using SceneKit, but I didn’t know it was going to be exclusive to Apple’s own wireless charger.

How does it work?

iOS has several apps with names ending in “ViewService” that aren’t “normal” apps, they are apps that run triggered by something else on the system and usually draw UI on top of another context such as an app, the home screen or the lock screen.

An example of such a view service is SharingViewService, which is the app responsible for presenting things such as the AirPods and HomePod pairing UI and AirDrop. On my Tweet I included earlier I mention ChargingViewService. That’s an app that was included with the iOS 11 GM and the first public releases of iOS 11 but then got removed in iOS 11.2, probably since it was not needed yet given that AirPower was (and still is) not available.

ChargingViewService is the process responsible for showing the cool animation. When the device is connected to power, a daemon called sharingd detects the presence of a power source for the device, it then checks to see if the power source is a wireless charger manufactured by Apple and then triggers the presentation of the charging UI.

Initially, ChargingViewService creates a view controller called InitialViewController (a note: the lack of a prefix does not mean that this component is written in Swift, in fact it isn’t). InitialViewController receives information from sharingd about the devices being charged and their power sources. On viewDidAppear, it adds a child view controller to itself called CallistoViewController (Callisto is the codename for the AirPower project).

CallistoViewController is responsible for showing a list of devices being charged with their icon, name and battery indicator. Each item on that list is called a platter. But before the platter is shown to the user, CallistoViewController presents yet another view controller, called EngagementViewController.

EngagementViewController is the view controller responsible for displaying the 3D animation when a new device is engaged on the same wireless power source as the key device. As far as I can see, the “key device” is the main device in the group of devices being charged, that is, the iPhone. The first engagement animation to be displayed is the one for the key device itself.

When EngagementViewController appears on screen, it calls a method on itself called startEngagement, which in turn configures a view called EngagementView with several properties about the device being engaged.

To be able to display the engagement animation for a device, EngagementView needs to be configured with several assets that define how the animation is going to work and also provide the visual assets for the animation.

These assets are not currently present in any public build of iOS, they are downloaded on demand from Apple’s main entry software update server. This makes sense since most people won’t even have an AirPower and even then most people won’t have all devices that can be charged with an AirPower, so only the assets that are actually needed are downloaded and installed to /System/Library/PreinstalledAssetsV2/RequiredByOS.

Another thing that’s done during this process is a capture of the device’s wallpaper through a call to SBSUIWallpaperGetPreview . Yes, the virtual device on screen will show up with the actual wallpaper that’s set up on your device.

The most important asset for the animation is a video file, usually called Charging.mov (AirPods have other video files for Left-only, Right-only and Right+Left). This video file consists of two videos side-by-side: one of them is the color video of the 3D device animating into the screen and then rotating and the other one is the same content, but represented as an alpha mask.

Another asset is a SceneKit scene file that contains a plane matching the position of the device’s screen throughout the animation (it lines up with the video). When the engagement animation is presented, the video is sliced in half and is used as a texture in a SceneKit scene, with the color part being used as the diffuse texture and the alpha part being used as the transparency texture, resulting in a video with a transparent background. The wallpaper is composed on top of the video with the plane provided by the scene.

Only part of the animation is actually provided by the video and scene files. As can be seen in the video above, after the initial appearance of the device, it rotates quickly, but doesn’t shrink down as if it’s flying back, which is what it does in the actual UI.

That “flying back” animation is configured through a series of plist files which are also a part of the assets downloaded from mesu. There are different permutations of the files for each iPhone screen size and also variants for right-to-left languages.

The engagement view uses those plist files to drive the animation of the device flying into its platter, which is accomplished by transforming the SceneKit scene view containing the video and screen of the device. At the end of the animation, a snapshot is taken which becomes the static image of the device that can be seen on the platter.

Making it run on an iPhone 5s

I keep a jailbroken iPhone 5s around just for experimentation. The iOS version I’m running on that device (11.1.2) happens to include ChargingViewService. I had been able to activate the UI before, but only showing the platters since I didn’t have the animation assets.

Recently, I got my hands on those assets for the Apple Watch Series 3 and the AirPods with the wireless charging case, so I decided to give it a go again and try to make the engagement animation run.

The process to make it run was very similar to the one I described on my NSSpain talk this year. Initially, I made a framework which I called ChargingHack and modified the load commands for the ChargingViewService binary so it would include a LOAD_DYLIB call to that.

Later on, I figured out a more simple way of doing things: I made a new app in Xcode called HackyViewService and removed the “compile sources” build phase from it, added everything from ChargingViewService.app into the project, removing the code signature and changed the LOAD_DYLIB command to load ChargingHack from @executable_path/Frameworks . This works because ChargingViewService doesn’t require any special entitlements to present the engagement animation and platters and because I’m running on a jailbroken device. This was a much better way to tinker with ChargingViewService since I could run it directly from Xcode.

What I do in ChargingHack is again very similar to my NSSpain demo: I swizzle some methods to make things work the way I want them to, then instantiate InitialViewController and “make it believe” it’s seeing an Apple Watch being charged through AirPower.

I’m not going to post the code on-line (yet) because I plan on using this as a demo in workshops and other talks, so keep an eye out for that.