For some reason, maybe just because it’s cool to know, I’m particularly interested in knowing how systems work. What’s really going on when I press the build button in Xcode? How do applications launch on iOS? How does it really work? So today I thought I’d do a quick guide on accessing and using Apple’s private APIs to hopefully give you some insight.
We’re gonna start by adding the following view to a view controller. This is a view Apple created when they introduced FaceID, and it’s a private API, meaning we can’t use it in the normal way we’re used to with public APIs. So we have to be more tricky. Let’s take a look.
Before I go on, just note that this application will need to be run on an actual device, as this framework isn’t available on the simulator. First, let’s grab the actual class type of this view so we can create an instance of it. The class is called LAUIPearlGlyphView. Since we’re not importing a framework at the top of our Swift file, we can’t just call LAUIPearlGlyphView(). Instead we need to create this class type from the NSClassFromString function from the Objective-C runtime. This returns us a Swift.AnyClass? type. In viewDidLoad of your UIViewController class, write the following line of code:
You’ll notice I named the variable with a capital. This is because this is a class type, not an instance of a class. To get an instance of this class, we need to call .init() on it. We can assign the result of this .init() to a property on the ViewController, and then add it as a subview of our view.
And that’s it! We’ve got our class by passing a string, called init on that class and added it to our subviews. Now if you try and build and run on your device, you’ll notice that the app crashes when trying to force cast our AnyClass? to a UIView.Type in the NSClassFromString function. The reason is because the framework’s executable code hasn’t been loaded into a running program. The framework this view is included in is LocalAuthenticationPrivateUI.framework. This is a dynamically loaded bundle, meaning the framework isn’t available for us to use when we try to use it. To overcome this issue, we’ll make use of the Bundle class, using the (path: String) initialiser.
Before you call the the NSClassFromString function, add the following line of code. I’ve put this in didFinishLaunchingWithOptions in the AppDelegate, but putting it in viewDidLoad just before calling NSClassFromString will work too.
This is also the reason why you can’t run this on the simulator, as a framework at this path exists only on real devices running iOS 11. Build and run again, and you should see the face view show up! Great! It’s a pretty picture sure, but we want it to be more than just a static view. We want it to do stuff.
Because our faceIDView is typed as a UIView, we can’t call functions and access properties with the usual syntax. Instead we have to use setKey and perform functions, which are on UIView. These take in a string, representing the method/property you want to access. For this demo, we want to access the state property, and the _applyStateAnimated: selector to perform the animation above. To perform the animation, you first change the state to an Int between 0-6, and then call the _applyStateAnimated: selector.
Override touchesBegan on your view controller and add the following two lines. This will change the state property and animate the view to the new state.
Build and run again and tap the screen, and you should see your face go wild in the circle animation. Just try not to get hypnotised by it. You can try them all out by changing the state to an Int anywhere from 0-6 and then calling the _applyStateAnimated: selector to see all the animations possible.
Now if you’re interested in more, you can check out everything this view can do here. This is the header file for the view. And for even further reading, check out this entire website, which shows you all the headers for iOS 11 and lower. And there’s also this site, giving you visibility of even more things, past and present. Try them out for yourself if you want, just remember to change the path of the Bundle if you’re using a different framework!
See you next time!