Intro
While I was preparing a large article about one of the Application Extensions, management decided to make some small UI enhancements and add extra logic to our utility app. A couple of weeks ago, this would have been a quick task — just a couple of hours of work. But… our Network Extension has two major limitations:
Running/Debugging is not possible on Simulator because of the system frameworks usage and can be achieved only on Real Device. It’s fine and can be easily avoided.
Embedded library precompiled for arm64. Custom library which relies on specific
Go
runtime - and that’s breaking a Debug built for Simulator to fail.
Problem
I really adore Xcode Preview — the handy tool for working with either UIKit or SwiftUI and instantly seeing changes on the live canvas. Having to work and update the app only on a real device is time-consuming and feels like going back to the pre-PreviewProvider
days.
In the current state, Xcode Preview doesn’t work at all. Let’s fix that. The steps are plain and straightforward. This post is mainly a memo, because this issue can (and will) appear with any Application Extension.
Steps
Xcode Preview runs a Debug build for content generation. For speed and convenience. So all Release configs should be left as is while others need to be changed.
Exclude target from simulator Debug builds
Navigate to your Extension Target
Open Build Settings
Add arm64 to Exclude Architectures
Isolate Extension logic if needed
3rd party library or Framework is not included in Debug build now and we need to adjust the codebase. It can be complicated or a fast trip which is totally relies on applied logic.
In example, for Network Extension, we need to exclude import and provide basic non-dependent class:
// Native targetEnvironment if-then-else
#if targetEnvironment(simulator)
import NetworkExtension
class AppProxyProvider: NEAppProxyProvider {
override func startProxy(options: [String : Any]? = nil, completionHandler: @escaping (Error?) -> Void) {
// Add code here to start the process of connecting the tunnel.
}
override func stopProxy(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
// Add code here to start the process of stopping the tunnel.
completionHandler()
}
}
#else
import NetworkExtension
import ExternalLibrary
class AppProxyProvider: NEAppProxyProvider {
private let externalLibrary = ExternalLibrary()
override func startProxy(options: [String : Any]? = nil, completionHandler: @escaping (Error?) -> Void) {
// Add code here to start the process of connecting the tunnel.
externalLibrary.apply()
}
override func stopProxy(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
// Add code here to start the process of stopping the tunnel.
externalLibrary.transfer()
completionHandler()
}
}
#endif
That’s it — short and sweet.
Happy coding!