Skip to content

Add Swift bindings and sample apps#834

Open
tsvvladimir wants to merge 6 commits into
brainflow-dev:masterfrom
tsvvladimir:feature-swift-bindings
Open

Add Swift bindings and sample apps#834
tsvvladimir wants to merge 6 commits into
brainflow-dev:masterfrom
tsvvladimir:feature-swift-bindings

Conversation

@tsvvladimir

@tsvvladimir tsvvladimir commented May 18, 2026

Copy link
Copy Markdown

Summary

  • Add a Swift Package Manager package with Swift bindings for BoardShim, DataFilter, MLModel, BrainFlow params, errors, enums, and runtime native-library loading.
  • Add Swift tests and examples modeled after existing BrainFlow binding examples, including synthetic board data, markers, file IO, downsampling, transforms, filtering, denoising, band powers, ICA, and MLModel prediction.
  • Add Swift CLI, macOS SwiftUI demo, iOS/macOS sample app assets under swift_package/examples/apps, App Store readiness notes, docs, and macOS CI coverage for Swift build/test/CLI/examples.

Verification

  • python3 tools/build.py --clear-build-dir --num-jobs 4 --no-synchroni --no-bluetooth --no-ble --no-tests --cmake-osx-architectures arm64
  • env BRAINFLOW_LIB_DIR=../installed/lib swift build
  • env BRAINFLOW_LIB_DIR=../installed/lib swift test
  • all Swift example products with env BRAINFLOW_LIB_DIR=../installed/lib swift run <example>
  • env BRAINFLOW_LIB_DIR=../installed/lib swift run brainflow-swift-cli
  • xcodebuild -quiet -project swift_package/examples/apps/ios/BrainFlowiOSDemo/BrainFlowiOSDemo.xcodeproj -scheme BrainFlowiOSDemo -configuration Debug -destination id=22FB561A-9754-45E2-92CF-D16F1F84FAC4 -derivedDataPath /private/tmp/brainflow-ios-derived-review build
  • iOS simulator autorun on iPhone 17 Pro reported Rows: 32, Samples: 533 and rendered EEG plot
  • make html SPHINXBUILD=/private/tmp/brainflow-docs-venv-20260518/bin/sphinx-build
  • git diff --check
  • macOS SwiftUI demo launched with synthetic board and reported Rows: 32, Samples: 521

Notes

  • Swift package loading uses dlopen and does not vendor native binaries; native BrainFlow libraries must be built separately and placed in BRAINFLOW_LIB_DIR, system library paths, installed/lib, or an app bundle resource/framework directory.
  • iOS/App Store runtime support requires target-specific BrainFlow native libraries or XCFrameworks embedded and signed in the app bundle.
  • App Store submission still needs production bundle IDs, signing/provisioning, screenshots, icons, and App Store Connect records.

@Andrey1994 Andrey1994 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrote some comments, but looks good overall, main concern is about packaging

if: (matrix.os == 'macos-14')
run: |
cd $GITHUB_WORKSPACE/swift_package
BRAINFLOW_LIB_DIR=$GITHUB_WORKSPACE/installed/lib swift run brainflow-swift-cli

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add all examples to CI as in brainflow dosc https://brainflow.readthedocs.io/en/stable/Examples.html#python

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in c030c76. I added all documented Swift examples as SwiftPM executable products and added a macOS CI step that runs them all with BRAINFLOW_LIB_DIR=$GITHUB_WORKSPACE/installed/lib swift run <example>.

Comment thread docs/requirements.txt Outdated
@@ -1,4 +1,9 @@
sphinx==4.0.2
sphinxcontrib-applehelp==1.0.2

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is it needed? doesnt look relevant to the binding


stopStream()
readData()
let rows = rowCount

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for demo app for ias lets add a plot for eeg channels, also keep it under swift_package folder, dont add it to the root folder.
So folder structure will be like:

  • swift_package
    - brainflow
    - examples


private func startStream() {
do {
let board = try BoardShim(board_id: BoardIds.SYNTHETIC_BOARD)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it possible to use non synthetic board, devices we care about are Muse boards(no BLED dongle but native BLE boards should work)

@@ -0,0 +1,44 @@
# BrainFlow iOS Demo

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont create folders like samples in the root folder. Instructions to build and use the binding should be in Docs

import BrainFlow

let data = Array(0..<256).map { sin(Double($0) / 10.0) }
let psd = try DataFilter.get_psd(data: data, start_pos: 0, end_pos: data.count, sampling_rate: 250, window: WindowOperations.HANNING.rawValue)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont use synathetic data like this, get it from synthetic board instead. Check how code samples referenced from docs are structured and make it the same way for consistency. Update docs to include swift binding code samples

@@ -0,0 +1,13 @@
import BrainFlow

var data = Array(0..<256).map { sin(Double($0) / 10.0) }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here and for all other examples

@@ -0,0 +1,5 @@
import BrainFlow

let data = Array(0..<256).map(Double.init)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

init(names: [String]) throws {
var errors = [String]()
for path in Self.candidatePaths(for: names) {
if let handle = dlopen(path, Self.openFlags) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its good, I like dlopen approach like in other bindings, but how does packaging work? native libs should be installed and compiled separately? Did you find a way to add them to the package like we do in jar and whl? How will it work for ios and publishing to appstore?

Copy link
Copy Markdown
Member

Addressed the review feedback in c030c76:

  • Removed the unrelated docs/requirements.txt dependency additions.
  • Moved iOS/macOS sample app files from root samples/ into swift_package/examples/apps/... and updated docs accordingly.
  • Added all documented Swift examples as SwiftPM executable products and added a macOS CI step to run them all.
  • Reworked Swift signal-processing examples to read real data from BoardShim synthetic board instead of local generated arrays.
  • Added shared Swift example support for synthetic-board reads.
  • Updated docs to include Swift example run instructions and native-library packaging behavior.
  • Expanded the iOS demo with Muse/native BLE board selection fields and a live EEG channel plot from BrainFlow data.
  • Clarified iOS/App Store packaging: Swift package does not vendor native binaries; apps must embed/sign target-specific libBoardController, libDataHandler, and libMLModule libraries or XCFrameworks.

Local verification:

  • env BRAINFLOW_LIB_DIR=../installed/lib swift build
  • env BRAINFLOW_LIB_DIR=../installed/lib swift test
  • all Swift example products via swift run
  • env BRAINFLOW_LIB_DIR=../installed/lib swift run brainflow-swift-cli
  • iOS Xcode simulator build and autorun on iPhone 17 Pro simulator, showing 32 rows / 533 samples and EEG plot
  • docs build with make html SPHINXBUILD=/private/tmp/brainflow-docs-venv-20260518/bin/sphinx-build
  • git diff --check

@tsvvladimir

Copy link
Copy Markdown
Author

Clarification: the previous review-response comment was accidentally posted through a GitHub connector authenticated as Andrey1994. This follow-up is posted from tsvvladimir. The branch and commits are from tsvvladimir; the review fixes are in c030c762 on feature-swift-bindings. Summary: Swift examples were added to CI, app samples were moved under swift_package/examples/apps, synthetic generated arrays were replaced with synthetic-board reads, the iOS demo now has Muse/native BLE options plus an EEG plot, unrelated docs dependency changes were removed, and packaging/App Store notes were clarified.

@Andrey1994

Copy link
Copy Markdown
Member

@kcoul could you help reviewing this? I dont have enough swift experience, may overlap with the work you are doing

@tsvvladimir tsvvladimir force-pushed the feature-swift-bindings branch 4 times, most recently from ed38a5f to 60f1fc1 Compare June 8, 2026 06:40

@Andrey1994 Andrey1994 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we cannot have copies of all headers from C interface in XCFrameworks format, lets come up with the script(CMake commands) to automate this copying step and dont have it as a part of the repo

@@ -0,0 +1,40 @@
# Swift API Parity

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we have docs in swift api package folder, they are not rendered anywhere. Move it please to docs in the root folder instead

for (const std::string &candidate : get_dlopen_candidates ())
{
return false;
lib_instance = dlopen (candidate.c_str (), RTLD_LAZY | RTLD_DEEPBIND);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all of this makes its unknown which library is actually loaded, lets add log messages at least with the full path for the lib


private:
#ifndef _WIN32
std::vector<std::string> get_dlopen_candidates () const

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does it work for windows? I see this method is called from another one, but for _WIN32 macro its not defined anywhere?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to make it cleaner change it to smth like ifdef APPLE all this logic for different paths, else just normal library path as before

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this file?

@@ -0,0 +1,498 @@
#pragma once

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it a copypaste from c++ code? why is it needed here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this file?

@@ -0,0 +1,61 @@
#pragma once

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we need headers from C++ in swift, we should handle it as a part of CMake call, do not copypaste files manually

#pragma once

#include "shared_export.h"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same, move file copy to CMake call and dont copy them

@@ -0,0 +1,8 @@
#pragma once

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same about copying

@@ -0,0 +1,498 @@
#pragma once

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same about copying

@tsvvladimir tsvvladimir force-pushed the feature-swift-bindings branch from 60f1fc1 to 953d653 Compare June 9, 2026 06:37
@tsvvladimir tsvvladimir force-pushed the feature-swift-bindings branch from 953d653 to 08f0601 Compare June 9, 2026 06:39
@tsvvladimir

Copy link
Copy Markdown
Author

Addressed the latest Apple packaging review in 08f0601.

What changed:

  • Removed generated Apple XCFramework/header/binary artifacts from Git tracking. swift_package/Artifacts/Apple is now ignored.
  • Kept header/framework copying inside tools/apple/build_xcframeworks.sh, so Apple artifacts are regenerated from the current source/install tree instead of copied into the repo.
  • Updated CI to build, verify, smoke-test, package, and upload generated Apple artifacts from build/apple_xcframeworks. The uploaded set now includes the release zip, zip checksum, per-file checksums, and manifest.
  • Updated iOS/macOS app defaults and docs to consume build/apple_xcframeworks/XCFrameworks.
  • Added release-maintenance guidance: build from a release tag, set BRAINFLOW_VERSION, regenerate, verify, publish zip/checksums/manifest together, and do not hand-edit generated frameworks.
  • Fixed the clang-format issue in runtime_dll_loader.h.
  • Removed the previously unrelated docs/requirements.txt changes.

Local verification:

  • tools/apple/regenerate_artifacts.sh full rebuild passed.
  • tools/apple/regenerate_artifacts.sh --skip-build refresh passed after generator README update.
  • tools/apple/test_swift_binary_package.sh build/apple_xcframeworks passed with synthetic board data.
  • env BRAINFLOW_LIB_DIR=../installed/lib swift test passed.
  • All Swift example products ran successfully.
  • macOS demo packaged from regenerated XCFrameworks and autorun passed: 32 rows / 524 samples.
  • iOS simulator build passed against regenerated XCFrameworks; iPhone simulator autorun showed 32 rows / 522 samples and populated EEG plot.
  • Docs build passed in venv; existing unrelated warnings remain.
  • git diff --check and Apple shell bash -n passed.

Current GitHub Actions runs for 08f0601 are action_required pending maintainer approval for fork PR workflows.

@tsvvladimir

Copy link
Copy Markdown
Author

Follow-up in 0eef31b adapts the Apple artifact workflow to Apple's SwiftPM binary framework guidance.

What changed:

  • The generator now produces individual SwiftPM release ZIPs at SwiftPMArtifacts/*.xcframework.zip, each with the .xcframework at the ZIP root.
  • SwiftPM checksums are generated with swift package compute-checksum and written to swiftpm-checksums.txt and swiftpm-checksums.json.
  • The generator now creates BrainFlowSwiftPackageRemote with URL-based binaryTarget declarations for BoardController, DataHandler, and MLModule.
  • BRAINFLOW_APPLE_RELEASE_BASE_URL controls the public release URL prefix used in the generated remote Package.swift.
  • manifest.json now records the SwiftPM release URL base and binary target checksums.
  • verify_xcframeworks.sh now enforces the SwiftPM ZIP layout, recomputes checksums, and validates the generated remote package manifest.
  • CI uploads the individual SwiftPM ZIPs, generated remote package, SwiftPM checksum files, aggregate archive, and manifest.
  • Docs and AGENTS.md now describe the maintainable release/update flow.

Validation:

  • tools/apple/build_xcframeworks.sh --skip-build --output build/apple_xcframeworks passed.
  • tools/apple/verify_xcframeworks.sh build/apple_xcframeworks passed.
  • tools/apple/test_swift_binary_package.sh build/apple_xcframeworks passed with synthetic board data.
  • tools/apple/package_macos_demo_app.sh /private/tmp/BrainFlowMacDemo-apple-guidance.app passed.
  • iOS simulator generic build passed against regenerated XCFrameworks.
  • docs build passed in venv with only existing unrelated warnings.
  • bash -n and git diff --check passed.

GitHub Actions for 0eef31b are still action_required pending maintainer approval for fork PR workflows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants