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 Package
s, 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;
@end
In 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;
}
@end
While 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.