diff --git a/.github/workflows/ios-spm-build-test.yml b/.github/workflows/ios-spm-build-test.yml new file mode 100644 index 0000000000..085b949e90 --- /dev/null +++ b/.github/workflows/ios-spm-build-test.yml @@ -0,0 +1,115 @@ +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: + 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: GHSpmTest + 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 + if: matrix.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: 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: | + xcodebuild build \ + -project "${{ env.APP_NAME }}.xcodeproj" \ + -scheme "${{ env.APP_NAME }}" \ + -configuration Debug \ + -sdk iphonesimulator \ + -destination 'generic/platform=iOS Simulator' \ + -quiet 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..1226a29728 --- /dev/null +++ b/packages/react-native-gesture-handler/Package.swift @@ -0,0 +1,84 @@ +// swift-tools-version: 6.0 + +import Foundation +import PackageDescription + +let packageDirectory = Context.packageDirectory + +func rnWorkletsPackageExists() -> Bool { + let url = URL(fileURLWithPath: packageDirectory) + .deletingLastPathComponent() + .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"], + cSettings: cSettings, + cxxSettings: cxxSettings, + linkerSettings: [ + .linkedFramework("UIKit"), + .linkedFramework("Foundation"), + .linkedFramework("CoreGraphics"), + ] + ), + ], + cxxLanguageStandard: .cxx20 +) 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 e703ae4a22..3b00535a76 100644 --- a/packages/react-native-gesture-handler/package.json +++ b/packages/react-native-gesture-handler/package.json @@ -24,6 +24,8 @@ "module": "lib/module/index.js", "types": "lib/typescript/index.d.ts", "files": [ + "Package.swift", + "include/", "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: {