From 5ecb5d97dec8a30e04d2b4ab3018965a72ec006f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Fri, 11 Sep 2026 12:44:31 +0200 Subject: [PATCH 1/7] feat(iOS): add SPM build support --- .../react-native-gesture-handler/.gitignore | 5 ++ .../Package.swift | 83 +++++++++++++++++++ .../react-native-gesture-handler/package.json | 1 + .../react-native.config.js | 3 + 4 files changed, 92 insertions(+) create mode 100644 packages/react-native-gesture-handler/Package.swift diff --git a/packages/react-native-gesture-handler/.gitignore b/packages/react-native-gesture-handler/.gitignore index 7e5de6786f..e411f72703 100644 --- a/packages/react-native-gesture-handler/.gitignore +++ b/packages/react-native-gesture-handler/.gitignore @@ -66,6 +66,11 @@ jsconfig.json !.yarn/versions +# Swift Package Manager +.build/ +.swiftpm/ +Package.resolved + # TS dist/ diff --git a/packages/react-native-gesture-handler/Package.swift b/packages/react-native-gesture-handler/Package.swift new file mode 100644 index 0000000000..09c3edf1b5 --- /dev/null +++ b/packages/react-native-gesture-handler/Package.swift @@ -0,0 +1,83 @@ +// swift-tools-version: 6.0 + +import Foundation +import PackageDescription + +let packageDirectory = Context.packageDirectory + +func rnWorkletsPackageExists() -> Bool { + let url = URL(fileURLWithPath: packageDirectory) + .appendingPathComponent("../RNWorklets/Package.swift") + return FileManager.default.fileExists(atPath: url.path) +} + +let useWorklets = rnWorkletsPackageExists() + +let reactHeaders: [Target.Dependency] = [ + .product(name: "ReactHeaders", package: "ReactNative"), + .product(name: "ReactNativeHeaders", package: "ReactNative"), + .product(name: "ReactNativeDependenciesHeaders", package: "ReactNative"), + .product(name: "ReactAppHeaders", package: "React-GeneratedCode"), +] + +var packageDependencies: [Package.Dependency] = [ + .package(name: "ReactNative", path: "../../../../xcframeworks"), + .package(name: "React-GeneratedCode", path: "../../../ios"), +] + +var targetDependencies: [Target.Dependency] = reactHeaders + +if useWorklets { + packageDependencies.append(.package(name: "RNWorklets", path: "../RNWorklets")) + targetDependencies.append(.product(name: "RNWorklets", package: "RNWorklets")) +} + +var cSettings: [CSetting] = [ + .headerSearchPath("apple"), + .headerSearchPath("shared/runtime"), + .headerSearchPath("shared/shadowNodes"), + .headerSearchPath( + "shared/shadowNodes/react/renderer/components/rngesturehandler_codegen"), + .define("DEBUG", .when(configuration: .debug)), + .define("NDEBUG", .when(configuration: .release)), +] + +var cxxSettings: [CXXSetting] = [ + .headerSearchPath("apple"), + .headerSearchPath("shared/runtime"), + .headerSearchPath("shared/shadowNodes"), + .headerSearchPath( + "shared/shadowNodes/react/renderer/components/rngesturehandler_codegen"), + .define("DEBUG", .when(configuration: .debug)), + .define("NDEBUG", .when(configuration: .release)), +] + +if useWorklets { + cxxSettings.append(.define("RNGH_USE_WORKLETS", to: "1")) +} + +let package = Package( + name: "RNGestureHandler", + platforms: [.iOS(.v15)], + products: [ + .library(name: "RNGestureHandler", targets: ["RNGestureHandler"]), + ], + dependencies: packageDependencies, + targets: [ + .target( + name: "RNGestureHandler", + dependencies: targetDependencies, + path: ".", + exclude: ["apple/RNGestureHandler.xcodeproj"], + sources: ["apple", "shared"], + publicHeadersPath: "apple", + cSettings: cSettings, + cxxSettings: cxxSettings, + linkerSettings: [ + .linkedFramework("UIKit"), + .linkedFramework("Foundation"), + ] + ), + ], + cxxLanguageStandard: .cxx20 +) diff --git a/packages/react-native-gesture-handler/package.json b/packages/react-native-gesture-handler/package.json index e703ae4a22..664bbf6c82 100644 --- a/packages/react-native-gesture-handler/package.json +++ b/packages/react-native-gesture-handler/package.json @@ -24,6 +24,7 @@ "module": "lib/module/index.js", "types": "lib/typescript/index.d.ts", "files": [ + "Package.swift", "src", "lib", "!**/__tests__", diff --git a/packages/react-native-gesture-handler/react-native.config.js b/packages/react-native-gesture-handler/react-native.config.js index d19502f02d..fbb5ecbe7b 100644 --- a/packages/react-native-gesture-handler/react-native.config.js +++ b/packages/react-native-gesture-handler/react-native.config.js @@ -1,4 +1,7 @@ module.exports = { + spm: { + name: 'RNGestureHandler', + }, dependency: { platforms: { android: { From 4d2c4f2221370ec76c1e0b139037b45ddf98ce3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Fri, 11 Sep 2026 12:52:34 +0200 Subject: [PATCH 2/7] ci: add workflow for blank RN app SPM build --- .github/workflows/ios-spm-build-test.yml | 97 ++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 .github/workflows/ios-spm-build-test.yml diff --git a/.github/workflows/ios-spm-build-test.yml b/.github/workflows/ios-spm-build-test.yml new file mode 100644 index 0000000000..e796ea00a8 --- /dev/null +++ b/.github/workflows/ios-spm-build-test.yml @@ -0,0 +1,97 @@ +name: Build iOS (SPM) + +on: + pull_request: + paths: + - packages/react-native-gesture-handler/Package.swift + - packages/react-native-gesture-handler/package.json + - packages/react-native-gesture-handler/react-native.config.js + - packages/react-native-gesture-handler/package.json + - packages/react-native-gesture-handler/RNGestureHandler.podspec + - packages/react-native-gesture-handler/apple/** + - packages/react-native-gesture-handler/shared/** + - packages/react-native-gesture-handler/src/** + - rnrepo.config.json + - yarn.lock + - '!packages/react-native-gesture-handler/src/**/*.test.*' + - .github/workflows/ios-spm-build-test.yml + push: + branches: + - main + workflow_dispatch: + +concurrency: + group: ios-spm-${{ github.ref }} + cancel-in-progress: true + +jobs: + ios-spm-isolated: + if: github.repository == 'software-mansion/react-native-gesture-handler' + + runs-on: macos-26-xlarge + timeout-minutes: 60 + + env: + APP_NAME: GHSpmTest + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: Select Xcode + run: | + XCODE_APP="/Applications/Xcode_26.4.1.app" + if [ ! -d "$XCODE_APP" ]; then + echo "Xcode 26.4.1 is not installed on this runner. Available:" >&2 + ls -d /Applications/Xcode*.app >&2 + fi + echo "Using $XCODE_APP" + echo "DEVELOPER_DIR=$XCODE_APP/Contents/Developer" >> "$GITHUB_ENV" + DEVELOPER_DIR="$XCODE_APP/Contents/Developer" xcodebuild -version + + - name: Use Node.js 24 + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: yarn + + - name: Read React Native version + id: rn-version + run: | + RN_VERSION=$(node -p "require('./packages/react-native-gesture-handler/package.json').devDependencies['react-native']") + echo "version=$RN_VERSION" >> "$GITHUB_OUTPUT" + + - name: Install monorepo dependencies + run: yarn --immutable + + - name: Build and pack library + working-directory: packages/react-native-gesture-handler + run: | + yarn build + mkdir -p /tmp/packs + npm pack --pack-destination /tmp/packs + echo "GH_TARBALL=$(ls /tmp/packs/react-native-gesture-handler-*.tgz)" >> "$GITHUB_ENV" + + - name: Create blank RN app + run: | + npx @react-native-community/cli@latest init "${{ env.APP_NAME }}" \ + --version "${{ steps.rn-version.outputs.version }}" + + - name: Install GH from tarball + working-directory: ${{ env.APP_NAME }} + run: npm install "${{ env.GH_TARBALL }}" + + - name: SPM scaffold + working-directory: ${{ env.APP_NAME }} + run: npx react-native spm scaffold --deintegrate --yes + + - name: Build iOS (SPM) + working-directory: ${{ env.APP_NAME }}/ios + run: | + xcodebuild build \ + -project "${{ env.APP_NAME }}.xcodeproj" \ + -scheme "${{ env.APP_NAME }}" \ + -configuration Debug \ + -sdk iphonesimulator \ + -destination 'generic/platform=iOS Simulator' \ + -quiet \ No newline at end of file From c154ecba3d8d9738dcf94018c3cb2038c96468df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Mon, 14 Sep 2026 10:33:58 +0200 Subject: [PATCH 3/7] fix: review changes --- packages/react-native-gesture-handler/Package.swift | 2 +- packages/react-native-gesture-handler/apple/RNGHStylusData.h | 4 ++++ packages/react-native-gesture-handler/include/.gitkeep | 3 +++ packages/react-native-gesture-handler/package.json | 1 + 4 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 packages/react-native-gesture-handler/include/.gitkeep diff --git a/packages/react-native-gesture-handler/Package.swift b/packages/react-native-gesture-handler/Package.swift index 09c3edf1b5..38eb534f76 100644 --- a/packages/react-native-gesture-handler/Package.swift +++ b/packages/react-native-gesture-handler/Package.swift @@ -70,12 +70,12 @@ let package = Package( path: ".", exclude: ["apple/RNGestureHandler.xcodeproj"], sources: ["apple", "shared"], - publicHeadersPath: "apple", cSettings: cSettings, cxxSettings: cxxSettings, linkerSettings: [ .linkedFramework("UIKit"), .linkedFramework("Foundation"), + .linkedFramework("CoreGraphics"), ] ), ], diff --git a/packages/react-native-gesture-handler/apple/RNGHStylusData.h b/packages/react-native-gesture-handler/apple/RNGHStylusData.h index 0e950d095e..d442ec541f 100644 --- a/packages/react-native-gesture-handler/apple/RNGHStylusData.h +++ b/packages/react-native-gesture-handler/apple/RNGHStylusData.h @@ -8,6 +8,10 @@ #ifndef RNGHStylusData_h #define RNGHStylusData_h +#import +#import +#import + @interface RNGHStylusData : NSObject @property (atomic, assign) double tiltX; diff --git a/packages/react-native-gesture-handler/include/.gitkeep b/packages/react-native-gesture-handler/include/.gitkeep new file mode 100644 index 0000000000..f12f3ee8f7 --- /dev/null +++ b/packages/react-native-gesture-handler/include/.gitkeep @@ -0,0 +1,3 @@ +// Gesture Handler exposes no SPM-facing headers, so Package.swift omits publicHeadersPath. +// Without publicHeadersPath, SPM defaults to the "include/" directory. +// We need to provide the "include/" directory so the iOS SPM build succeeds. diff --git a/packages/react-native-gesture-handler/package.json b/packages/react-native-gesture-handler/package.json index 664bbf6c82..3b00535a76 100644 --- a/packages/react-native-gesture-handler/package.json +++ b/packages/react-native-gesture-handler/package.json @@ -25,6 +25,7 @@ "types": "lib/typescript/index.d.ts", "files": [ "Package.swift", + "include/", "src", "lib", "!**/__tests__", From 0a032ac02eea5542ef42a2800b1f01c42aabed5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Mon, 14 Sep 2026 11:22:24 +0200 Subject: [PATCH 4/7] ci: add gesture handler build with worklets --- .github/workflows/ios-spm-build-test.yml | 76 ++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/.github/workflows/ios-spm-build-test.yml b/.github/workflows/ios-spm-build-test.yml index e796ea00a8..ea06a18a84 100644 --- a/.github/workflows/ios-spm-build-test.yml +++ b/.github/workflows/ios-spm-build-test.yml @@ -85,6 +85,82 @@ jobs: working-directory: ${{ env.APP_NAME }} run: npx react-native spm scaffold --deintegrate --yes + - name: Build iOS (SPM) + working-directory: ${{ env.APP_NAME }}/ios + run: | + xcodebuild build \ + -project "${{ env.APP_NAME }}.xcodeproj" \ + -scheme "${{ env.APP_NAME }}" \ + -configuration Debug \ + -sdk iphonesimulator \ + -destination 'generic/platform=iOS Simulator' \ + -quiet + + ios-spm-with-worklets: + if: github.repository == 'software-mansion/react-native-gesture-handler' + + runs-on: macos-26-xlarge + timeout-minutes: 60 + + env: + APP_NAME: GHSpmWorkletsTest + WORKLETS_VERSION: 0.13.0-nightly-20260913-5336bb7f4 + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: Select Xcode + run: | + XCODE_APP="/Applications/Xcode_26.4.1.app" + if [ ! -d "$XCODE_APP" ]; then + echo "Xcode 26.4.1 is not installed on this runner. Available:" >&2 + ls -d /Applications/Xcode*.app >&2 + fi + echo "Using $XCODE_APP" + echo "DEVELOPER_DIR=$XCODE_APP/Contents/Developer" >> "$GITHUB_ENV" + DEVELOPER_DIR="$XCODE_APP/Contents/Developer" xcodebuild -version + + - name: Use Node.js 24 + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: yarn + + - name: Read React Native version + id: rn-version + run: | + RN_VERSION=$(node -p "require('./packages/react-native-gesture-handler/package.json').devDependencies['react-native']") + echo "version=$RN_VERSION" >> "$GITHUB_OUTPUT" + + - name: Install monorepo dependencies + run: yarn --immutable + + - name: Build and pack library + working-directory: packages/react-native-gesture-handler + run: | + yarn build + mkdir -p /tmp/packs + npm pack --pack-destination /tmp/packs + echo "GH_TARBALL=$(ls /tmp/packs/react-native-gesture-handler-*.tgz)" >> "$GITHUB_ENV" + + - name: Create blank RN app + run: | + npx @react-native-community/cli@latest init "${{ env.APP_NAME }}" \ + --version "${{ steps.rn-version.outputs.version }}" + + - name: Install GH from tarball + working-directory: ${{ env.APP_NAME }} + run: npm install "${{ env.GH_TARBALL }}" + + - name: Install react-native-worklets + working-directory: ${{ env.APP_NAME }} + run: npm install "react-native-worklets@${{ env.WORKLETS_VERSION }}" + + - name: SPM scaffold + working-directory: ${{ env.APP_NAME }} + run: npx react-native spm scaffold --deintegrate --yes + - name: Build iOS (SPM) working-directory: ${{ env.APP_NAME }}/ios run: | From ac152fd96c9b99176bf3813b041ed7e24d16525b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Mon, 14 Sep 2026 19:14:26 +0200 Subject: [PATCH 5/7] ci: use macos-26 --- .github/workflows/ios-spm-build-test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ios-spm-build-test.yml b/.github/workflows/ios-spm-build-test.yml index ea06a18a84..faec613c53 100644 --- a/.github/workflows/ios-spm-build-test.yml +++ b/.github/workflows/ios-spm-build-test.yml @@ -28,7 +28,7 @@ jobs: ios-spm-isolated: if: github.repository == 'software-mansion/react-native-gesture-handler' - runs-on: macos-26-xlarge + runs-on: macos-26 timeout-minutes: 60 env: @@ -99,7 +99,7 @@ jobs: ios-spm-with-worklets: if: github.repository == 'software-mansion/react-native-gesture-handler' - runs-on: macos-26-xlarge + runs-on: macos-26 timeout-minutes: 60 env: @@ -170,4 +170,4 @@ jobs: -configuration Debug \ -sdk iphonesimulator \ -destination 'generic/platform=iOS Simulator' \ - -quiet \ No newline at end of file + -quiet From 1f1e96168cf1f9926d183de7f9f7810a81f0fc5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Mon, 14 Sep 2026 19:51:02 +0200 Subject: [PATCH 6/7] fix: worklets exists path --- packages/react-native-gesture-handler/Package.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-native-gesture-handler/Package.swift b/packages/react-native-gesture-handler/Package.swift index 38eb534f76..1226a29728 100644 --- a/packages/react-native-gesture-handler/Package.swift +++ b/packages/react-native-gesture-handler/Package.swift @@ -7,7 +7,8 @@ let packageDirectory = Context.packageDirectory func rnWorkletsPackageExists() -> Bool { let url = URL(fileURLWithPath: packageDirectory) - .appendingPathComponent("../RNWorklets/Package.swift") + .deletingLastPathComponent() + .appendingPathComponent("RNWorklets/Package.swift") return FileManager.default.fileExists(atPath: url.path) } From 968cb1d40d10d35dc7562d428ff5193051aac4e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Mon, 14 Sep 2026 19:58:34 +0200 Subject: [PATCH 7/7] ci: simplify spm build --- .github/workflows/ios-spm-build-test.yml | 86 ++++-------------------- 1 file changed, 14 insertions(+), 72 deletions(-) diff --git a/.github/workflows/ios-spm-build-test.yml b/.github/workflows/ios-spm-build-test.yml index faec613c53..085b949e90 100644 --- a/.github/workflows/ios-spm-build-test.yml +++ b/.github/workflows/ios-spm-build-test.yml @@ -25,85 +25,19 @@ concurrency: cancel-in-progress: true jobs: - ios-spm-isolated: + ios-spm: if: github.repository == 'software-mansion/react-native-gesture-handler' runs-on: macos-26 timeout-minutes: 60 - env: - APP_NAME: GHSpmTest - - steps: - - name: checkout - uses: actions/checkout@v4 - - - name: Select Xcode - run: | - XCODE_APP="/Applications/Xcode_26.4.1.app" - if [ ! -d "$XCODE_APP" ]; then - echo "Xcode 26.4.1 is not installed on this runner. Available:" >&2 - ls -d /Applications/Xcode*.app >&2 - fi - echo "Using $XCODE_APP" - echo "DEVELOPER_DIR=$XCODE_APP/Contents/Developer" >> "$GITHUB_ENV" - DEVELOPER_DIR="$XCODE_APP/Contents/Developer" xcodebuild -version - - - name: Use Node.js 24 - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: yarn - - - name: Read React Native version - id: rn-version - run: | - RN_VERSION=$(node -p "require('./packages/react-native-gesture-handler/package.json').devDependencies['react-native']") - echo "version=$RN_VERSION" >> "$GITHUB_OUTPUT" - - - name: Install monorepo dependencies - run: yarn --immutable - - - name: Build and pack library - working-directory: packages/react-native-gesture-handler - run: | - yarn build - mkdir -p /tmp/packs - npm pack --pack-destination /tmp/packs - echo "GH_TARBALL=$(ls /tmp/packs/react-native-gesture-handler-*.tgz)" >> "$GITHUB_ENV" - - - name: Create blank RN app - run: | - npx @react-native-community/cli@latest init "${{ env.APP_NAME }}" \ - --version "${{ steps.rn-version.outputs.version }}" - - - name: Install GH from tarball - working-directory: ${{ env.APP_NAME }} - run: npm install "${{ env.GH_TARBALL }}" - - - name: SPM scaffold - working-directory: ${{ env.APP_NAME }} - run: npx react-native spm scaffold --deintegrate --yes - - - name: Build iOS (SPM) - working-directory: ${{ env.APP_NAME }}/ios - run: | - xcodebuild build \ - -project "${{ env.APP_NAME }}.xcodeproj" \ - -scheme "${{ env.APP_NAME }}" \ - -configuration Debug \ - -sdk iphonesimulator \ - -destination 'generic/platform=iOS Simulator' \ - -quiet - - ios-spm-with-worklets: - if: github.repository == 'software-mansion/react-native-gesture-handler' - - runs-on: macos-26 - timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + worklets: [false, true] env: - APP_NAME: GHSpmWorkletsTest + APP_NAME: GHSpmTest WORKLETS_VERSION: 0.13.0-nightly-20260913-5336bb7f4 steps: @@ -154,6 +88,7 @@ jobs: run: npm install "${{ env.GH_TARBALL }}" - name: Install react-native-worklets + if: matrix.worklets working-directory: ${{ env.APP_NAME }} run: npm install "react-native-worklets@${{ env.WORKLETS_VERSION }}" @@ -161,6 +96,13 @@ jobs: working-directory: ${{ env.APP_NAME }} run: npx react-native spm scaffold --deintegrate --yes + - name: Assert worklets are linked into RNGestureHandler + if: matrix.worklets + working-directory: ${{ env.APP_NAME }} + run: | + swift package --package-path ios/build/generated/autolinking show-dependencies --format dot \ + | grep 'libs/RNGestureHandler" -> ".*libs/RNWorklets"' + - name: Build iOS (SPM) working-directory: ${{ env.APP_NAME }}/ios run: |