Add Python to a SwiftUI application with the BeeWare Briefcase.
This project is derived from the tutorial provided by the BeeWare Project, reduced to those steps that are applicable to iOS development, then extended to add support for Swift.
With BeeWare, a template XCode project is generated using briefcase, which also provides a convenient way to manage requirements (Python libraries that are imported in your scripts).
The typical approach is to use Toga for UI development.
Many iOS developers are more familiar with UIKit and SwiftUI, or may require specialized iOS features.
Here, the Swift interoperability is achieved by creating an AppDelegate class in Objective C and creating an extension for that class in Swift, with supporting headers to form a bridge.
An optional step is to include PythonKit which provides a convenient syntax for calling Python objects in Swift, however, the Python C API may also be used.
A virtual environment is created below, which serves to isolate the Python environment used in creating the BeeWare project from your system default Python installation.
This is not required, but is recommended in the tutorial.
For the demonstration, sympy is used as an example "Pure-Python" library that will be tested in the app.
python3 -m venv venv
source venv/bin/activate
pip install sympy
python -m pip install briefcase
The command below will begin populating a directory with a name derived from the app name (in this example, beeswift) and various default scripts.
briefcase new
There will be several user prompts at this point, where you can choose either the defaults in brackets, or app-specific responses.
In this case, the app name Bee Swift and the description are provided, other defaults are accepted by typing enter at the prompt.
Formal Name [Hello World]: Bee Swift
App Name [beeswift]:
Bundle Identifier [com.example]:
Project Name [Bee Swift]:
Description [My first application]: Add Python to a SwiftUI application with the BeeWare Briefcase
Author [Jane Developer]:
Author's Email [[email protected]]:
Application URL [[https://example.com/beeswift]:
Project License [1]:
GUI Framework [1]:
Next we cd into the beeswift folder (or alternate app name), where we will modify the app requirements code.
In this example, we are using sympy, though for your application you will likely have other required Python modules to make your app work.
cd beeswift
Open the file pyproject.toml in a text editor, then scroll to the line that containes the requirements for the iOS app.
Specifically make sure this is under the # Mobile deployments line, and is preceeded by [tool.briefcase.app.beeswift.iOS].
Add sympy and the version number to the requires = ... section.
# Mobile deployments
[tool.briefcase.app.beeswift.iOS]
requires = [
'toga-iOS>=0.3.0.dev34',
'std-nslog~=1.0.0',
'sympy==1.11.1'
]
The command below will generate the ./iOS/XCode/Bee Swift/ directory, with Bee Swift.xcodeproj and various other support files.
From this point, you may navigate to this project file in Finder, and open it, as the subsequent steps will completed with XCode.
briefcase create iOS
In XCode, go to File -> Add Packages, click GitHub on the left hand side, click the plus button on the lower left.
In the search field in the upper right, enter the following
https://github.com/pvieito/PythonKit.git
Click Add Package in the lower right corner.
If you do choose to skip this optional step, then portions of the code in the AppDelegateExtension created in a later step will not work.
In XCode, in the tree on the left hand side, right click Supporting Files and then New File.
Select Header File in the menu, then click Next.
Enter the name AppDelegate then click Create, without modifying any defaults.
Under the commented section, delete any existing code, and replace with the following:
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@endIn Xcode, in the tree on the left hand side, right click Supporting Files and then New File.
Select Objective C File in the menu, then click Next.
Enter the name AppDelegate then click Next, and finally Create on the next window, without modifying any defaults.
Under the commented section, delete any existing code, and replace with the following.
#import "AppDelegate.h"
#include <Python.h>
#import "Bee_Swift-Swift.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self swiftuiExtension];
return YES;
}
@endWhile much of this is boiler-plate, there are two lines that should be highlighted.
First, the #import "Bee_Swift-Swift.h" line will form a bridge for this Objective C file to recognize its Swift counterpart.
Second, the [self swiftuiExtension]; will call the corresponding method that will be created in the next step.
In Xcode, in the tree on the left hand side, right click beeswift and then New File.
Select Swift File in the menu, then click Next.
Enter the name AppDelegateExtension then click Create, without modifying any defaults.
On the subsequent popup, click the option to Create Bridging Header.
Inside the newly created Bee Swift-Bridging-Header.h file add the following.
#include <Python.h>
#include <dlfcn.h>
#include "AppDelegate.h"
This will expose various C headers that we want to be available to Swift, without which we would not have access to Python or the ability to extend the AppDelegate.
Inside the new AppDelegateExtension.swift file, add the following.
import Foundation
import SwiftUI
import UIKit
import PythonKit
@objc public extension AppDelegate {
@objc func swiftuiExtension() {
// Do some Python stuff
let math = Python.import("math")
let pi = math.pi
let piStr = "pi = \(pi)"
let answer = "sin(1) = \(math.sin(1))"
// Do some sympy stuff
let sympy = Python.import("sympy")
let a = sympy.symbols("a")
let mechanics = Python.import("sympy.physics.mechanics")
let b = mechanics.dynamicsymbols("b")
let ab = a*b
let abStr = "x = \(ab)"
// Build a SwiftUI
self.window = UIWindow(frame: UIScreen.main.bounds)
window.makeKeyAndVisible()
window.rootViewController = UIHostingController(
rootView: VStack {
Text(piStr)
Text(answer)
Text(abStr)
}
)
}
}Here, the sections under the // Do some Python stuff and // Do some sympy stuff comments are included for example only, you will delete them to include your own code.
Importantly, the rootView argument to UIHostingController is where you will add your own SwiftUI views, in this case a vertical stack with three text fields is used to show the output of the Python code that preceded it.
Modify the call to UIApplicationMain in the main.m script, replacing @PythonAppDelegate with @AppDelegate.
UIApplicationMain(argc, argv, nil, @"AppDelegate");Click on the project (left upper on your Xcode window), then click on Build Phases tab and Compile Sources.
Check whether AppDelegate.m is added to the list.
If not, click plus button and add `AppDelegate.m.
If all the previous steps ran as-intended, then you should be able to run and see a simple app window, showing the value for pi, sine of 1, and a basic symbolic expression.
