Folder outline:
swift/
├── bridge/ # Swift-Rust bridge module (rust)
│ └── Cargo.toml
├── ios/
│ ├── Keyboard/ # Keyboard app extension (`.appex` Bundle)
│ ├── Khiin/ # Settings app (currently blank / for testing)
│ └── project.yml # `xcodegen` project config
├── osx/ # macOS input method swift package
│ └── Package.swift
└── shared/ # Shared macOS/iOS code
└── Package.swift
Khiin
: Currently a blank app with a text field simply for testing the IMEKeyboard
: The iOS Keyboard Extension (.appex
) Bundle codeproject.yml
: Builds the.xcodeproj
for the iOS appbridge
: A bridge module for Swift-Rust communication, usingswift-bridge
osx
: The macOS Input Methodshared
: A Swift Package for shared code, mostly to operate the bridge
Just run this:
brew install swift-protobuf xcodegen
rustup target add aarch64-apple-darwin x86_64-apple-darwin aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
cargo install --force swift-bridge-cli
cargo install --force cargo-make
cargo make
This will build all prereqs and the macOS input method. From here:
- The iOS app can be built in Xcode using the simulator directly.
- To rebuild the macOS IM after making changes, run:
cargo make build-khiinim
- Use
cargo-make
to run all of the build steps.
cargo install --force cargo-make
- Install the appropriate toolchains:
# For macOS
rustup target add aarch64-apple-darwin x86_64-apple-darwin
# For iOS
rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
# For iOS simulator
rustup target add aarch64-apple-ios-sim x86_64-apple-ios
- The following folders are generated during the build process:
swift/
├── bridge/
│ └── generated # Rust bridge module binaries
├── ios/
│ └── Khiin.xcodeproj # iOS app Xcode project
├── KhiinBridge # Swift package for bridge module
├── osx/
│ ├── .build
│ └── src/
│ └── protos # Protobuf generated files
└── shared/
└── .build
These folders will be removed with cargo make clean
.
cargo make build-swift-bridge
The bridge
module is compiled using
swift-bridge
into a KhiinBridge
Swift Package that can be consumed as a dependency. Building is a two-step
process.
- Use the
build.sh
(which usesbuild.rs
) to produce thelibkhiin_swift.a
binaries for each platform. - Use the
swift-bridge-cli
tool to package the generated code and the binaries into a Swift Package for use in XCode.
The package is generated to swift/KhiinBridge
, and can be added to XCode as a
normal local package dependency. The targets using this package must add a Build
Phase for the package.
- Right click the project, click
Add Packages...
->Add Local..
and select theKhiinBridge
folder - Navigate to the target ->
Build Phases
->Target Dependencies
and click the+
button, selecting `KhiinBridge - Repeat for
Link Binary With Libraries
This configuration should already be done, so you shouldn't need to change it.
The binaries produced by bridge/build.sh
are:
target/universal-ios/(debug|release)/libkhiin_swift.a
- for the simulatortarget/aarch64-apple-ios/(debug|release)/libkhiin_swift.a
- for iOS devicestarget/universal-macos/(debug|release)/libkhiin_swift.a
- for macOS devices
TODO: Repackage as a Swift Package like macOS app.
The XCode project must be built with xcodegen
, it does not get checked in to
the repo. The command is simply xcodegen
from the swift
folder. Or cargo make xcodegen
from the top.
When running the iOS simulator, XCode can be very flaky with respect to
rebuilding and running the latest code. If you need debugging (breakpoints and
logging), you must have your Scheme set to run the Keyboard
target, not the
Khiin
target. However, since the Khiin
target contains the Keyboard
target
as an embedded .appex
, I have found the best results by following these steps:
- Work on iOS code in the
Keyboard
folder - Build the
Khiin
target and run it on the simulator - Stop the execution of this process (hit the stop button at the top left)
- Build the
Keyboard
target, and select theKhiin
app to launch
This seems to work most of the time.
You will also need to enable the IME from the app settings to see it in the keyboard selection menu. Go to Settings > Khiin > Keyboards > Khiin PJH to enable it. Note that when selecting the keyboard, it is a bit flaky and does not always load properly the first time, you may have to select it twice. (I am told this is not a problem on actual devices, but I do not have one for testing.)
- The
Keyboard.appex
bundle will be embedded in theKhiin.app
bundle for delivery onto the device. - The iOS bundle identifier must start with something like
com.
ororg.
, and maybe a few others. However, we cannot use arbitrary bundle identifiers, as the original attempt to usebe.chiahpa
resulted in the IME not being recognized by the system. - The
Khiin
package (the app) must have aSettings.bundle
folder in order for the Keyboard extension to show up in system settings on iOS. The settings bundle does not need to have any properties or settings.
cargo make build-osx
The macOS input method is a normal OSX application. For an improved developer
experience, the input method is packaged for
SPM,
and a build script (swift/osx/build.sh
) places all of the necessary items into
the KhiinPJH.app
bundle folder, installed in ~/Library/Input Methods
. This
obviates the need for Xcode, and makes it possible to use other editors (namely
VSCode) for a better development experience.
The first time you run this command, you will need to log out and log back in to
activate the IME. During development, it is recommended to use the watch
version of the command, which will run in the background and update the package
every time you change the code.
cargo make build-osx # Run once first
cargo make watch-osx
Log output can be seen in another terminal with:
tail -f ~/Library/Caches/KhiinPJH/khiin_im.log
- The macOS Input Method bundle identifier must have
.inputmethod.
in the 3rd position. That is:a.b.inputmethod.c
is a valid bundle identifier. This is apparently not documented anywhere, but it was mentioned on some obscure websites and in old header files. - The first time you build the IM (
cargo make build-osx
), you need to log out and log back in to refresh the available input methods. Subsequent builds should "just work", but YMMV. The IM will be available under the "Taioanese" language in the system settings. - Resources: