### Install and Launch App Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/migration.md Installs the application onto the specified simulator and then launches it, capturing the output to extract the application's Process ID (PID). ```bash xcrun simctl install "$UDID" "$APP_PATH" LAUNCH_OUTPUT=$(xcrun simctl launch "$UDID" "$BUNDLE_ID") APP_PID=$(echo "$LAUNCH_OUTPUT" | grep -oE '[0-9]+$') echo "Launched with PID: $APP_PID" ``` -------------------------------- ### Manual Junie Global Skill Installation Source: https://github.com/frankois944/skills/blob/main/README.md Manually installs the spmforkmp skill for a Junie project globally. ```bash mkdir -p ~/.junie/skills git clone https://github.com/frankois944/skills.git temp_skills cp -r temp_skills/spmforkmp ~/.junie/skills/ rm -rf temp_skills ``` -------------------------------- ### Install spmforkmp Skill Globally Source: https://github.com/frankois944/skills/blob/main/README.md Installs the spmforkmp skill globally using the Agent Skills CLI. ```bash npx skills add -g frankois944/skills/spmforkmp ``` -------------------------------- ### Install spmForKmp Agent Skill Source: https://context7.com/frankois944/skills/llms.txt Install the skill project-level or globally using the Agent Skills CLI. Update an existing install or perform a manual install for Junie. ```bash npx skills add frankois944/skills/spmforkmp ``` ```bash npx skills add -g frankois944/skills/spmforkmp ``` ```bash npx skills update spmforkmp ``` ```bash mkdir -p .junie/skills git clone https://github.com/frankois944/skills.git temp_skills cp -r temp_skills/spmforkmp .junie/skills/ rm -rf temp_skills ``` -------------------------------- ### Manual Junie Project-level Skill Installation Source: https://github.com/frankois944/skills/blob/main/README.md Manually installs the spmforkmp skill for a Junie project at the project level. ```bash mkdir -p .junie/skills git clone https://github.com/frankois944/skills.git temp_skills cp -r temp_skills/spmforkmp .junie/skills/ rm -rf temp_skills ``` -------------------------------- ### Install spmforkmp Skill Project-level Source: https://github.com/frankois944/skills/blob/main/README.md Installs the spmforkmp skill for a specific project using the Agent Skills CLI. ```bash npx skills add frankois944/skills/spmforkmp ``` -------------------------------- ### Build and Launch iOS/tvOS/watchOS App (Simulator) Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/setup.md Sequence to build the KMP framework, pick/boot a simulator, build the Xcode project, install and launch the app, and verify it's running. Captures screenshots. ```bash set -o pipefail # propagate xcodebuild exit code through the pipe # 1. Build the KMP framework — use the suffix matching your configured target: # iOS: linkDebugFrameworkIosSimulatorArm64 # tvOS: linkDebugFrameworkTvosSimulatorArm64 # watchOS: linkDebugFrameworkWatchosSimulatorArm64 ./gradlew ::linkDebugFrameworkSimulatorArm64 # 2. Pick or boot a simulator for the target platform UDID=$(xcrun simctl list devices booted | grep -m1 -oE '[0-9A-F]{8}-[0-9A-F-]{27}') if [ -z "$UDID" ]; then # iOS: grep -m1 "iPhone" tvOS: grep -m1 "Apple TV" watchOS: grep -m1 "Apple Watch" UDID=$(xcrun simctl list devices available | grep -m1 "iPhone " | grep -oE '[0-9A-F]{8}-[0-9A-F-]{27}') xcrun simctl boot "$UDID" fi echo "Using simulator: $UDID" # 3. Build the Xcode project (Gradle run-script phase fires automatically) xcodebuild \ -project iosApp/iosApp.xcodeproj \ -scheme iosApp \ -configuration Debug \ -destination "platform=iOS Simulator,id=$UDID" \ -derivedDataPath build/xcode \ build 2>&1 | tail -n 50 # 4. Locate the .app and its bundle id APP_PATH=$(find build/xcode/Build/Products/Debug-iphonesimulator -maxdepth 1 -name "*.app" | head -1) BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$APP_PATH/Info.plist") # 5. Install and launch — capture the PID xcrun simctl install "$UDID" "$APP_PATH" LAUNCH_OUTPUT=$(xcrun simctl launch "$UDID" "$BUNDLE_ID") APP_PID=$(echo "$LAUNCH_OUTPUT" | grep -oE '[0-9]+$') echo "Launched with PID: $APP_PID" # 6. Verify the app is still running after 10 seconds sleep 10 if kill -0 "$APP_PID" 2>/dev/null; then echo "App is running — verification passed" else echo "App crashed after launch — verification FAILED" >&2 exit 1 fi # 7. screenshot cncrun simctl io "$UDID" screenshot build/xcode/launch_verify.png ``` -------------------------------- ### Install xcodeproj Gem Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/troubleshooting.md Install the xcodeproj Ruby gem to programmatically manage Xcode project files, useful for CI environments. ```bash gem install xcodeproj ``` -------------------------------- ### Kotlin Usage Example for Exported Package Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/exporting.md Example of how to use the exported Swift package in Kotlin. This demonstrates importing necessary classes and configuring the app. ```kotlin import FirebaseAnalytics.FIRConsentStatusGranted import FirebaseCore.FIRApp @ExperimentalForeignApi FIRApp.configure() val granted = FIRConsentStatusGranted ``` -------------------------------- ### Build and launch KMP app on iOS simulator Source: https://context7.com/frankois944/skills/llms.txt This script automates the process of building the KMP framework, selecting and booting an iOS simulator, building the Xcode project, and finally installing and launching the application on the simulator to verify the migration. ```bash # 1. Build the KMP framework ./gradlew :shared:linkDebugFrameworkIosSimulatorArm64 # 2. Pick or boot a simulator UDID=$(xcrun simctl list devices booted | grep -m1 -oE '[0-9A-F]{8}-[0-9A-F-]{27}') if [ -z "$UDID" ]; then UDID=$(xcrun simctl list devices available | grep -m1 "iPhone " | grep -oE '[0-9A-F]{8}-[0-9A-F-]{27}') xcrun simctl boot "$UDID" fi echo "Using simulator: $UDID" # 3. Build the Xcode project (Gradle run-script fires automatically) xcodebuild \ -project iosApp/iosApp.xcodeproj \ -scheme iosApp \ -configuration Debug \ -destination "platform=iOS Simulator,id=$UDID" \ -derivedDataPath build/xcode \ build # 4. Install and launch — a non-zero PID = success APP_PATH=$(find build/xcode/Build/Products -name "*.app" -not -path "*/PlugIns/*" | head -1) BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$APP_PATH/Info.plist") xcrun simctl install "$UDID" "$APP_PATH" xcrun simctl launch "$UDID" "$BUNDLE_ID" # 5. Confirm stability sleep 10 xcrun simctl io "$UDID" screenshot build/xcode/launch_verify.png ``` -------------------------------- ### Multiple Swift Packages Configuration Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/setup.md This example demonstrates how to configure multiple Swift package dependencies within a single `swiftPackageConfig` block. It includes remote versioned packages and a local binary framework. ```kotlin import io.github.frankois944.spmForKmp.swiftPackageConfig target.swiftPackageConfig("nativeBridge") { minIos = "16.0" dependency { remotePackageVersion( url = uri("https://github.com/firebase/firebase-ios-sdk.git"), version = "11.8.0", products = { add("FirebaseCore", exportToKotlin = true) add("FirebaseAnalytics", exportToKotlin = true) }, ) remotePackageVersion( url = uri("https://github.com/krzyzanowskim/CryptoSwift.git"), version = "1.8.4", products = { add("CryptoSwift", exportToKotlin = false) }, ) localBinary( path = "${rootDir}/Frameworks/VendorSDK.xcframework", packageName = "VendorSDK", exportToKotlin = false ) } } ``` -------------------------------- ### Update Minimum OS Version Example Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/setup.md Illustrates how to update the minimum iOS version requirement in `swiftPackageConfig` when a new dependency necessitates a higher version. ```swift // New dependency requires iOS 16 — current config has minIos = "15.0" // → raise to minIos = "16.0" platforms: [.iOS(.v16)] ``` -------------------------------- ### Swift Package Manager Configuration for Cinterop Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/exporting.md Example of a Swift Package Manager configuration block for cinterop, defining framework module and header requirements. ```swift framework module CryptoSwift { header "CryptoSwift.h" requires objc } ``` -------------------------------- ### XCFramework Modulemap Configuration Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/exporting.md Example of an XCFramework modulemap file. A compatible modulemap will point to an umbrella header that exposes Objective-C interfaces. ```text # ✅ ObjC-compatible — umbrella header points to real ObjC headers framework module VendorSDK { umbrella header "VendorSDK.h" export * module * { export *} } ``` -------------------------------- ### Swift Package Manager Target with Public Headers Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/exporting.md Examples of Swift Package Manager target definitions demonstrating Objective-C compatibility through `publicHeadersPath` or implicit header placement. ```swift // Explicit — ObjC-compatible .target(name: "FirebaseCore", publicHeadersPath: "Sources/Core/Public") // Implicit — ObjC-compatible if Sources/MyTarget/include/*.h exists .target(name: "MyTarget", ...) // Pure Swift — no publicHeadersPath, no include/ folder .target(name: "CryptoSwift", ...) ``` -------------------------------- ### Objective-C Header Declaration Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/exporting.md Example of an Objective-C header file. Look for actual interface declarations (`@interface`, `@protocol`) to confirm Objective-C compatibility. ```objc // ✅ ObjC-compatible — has real interface declarations @interface FIRApp : NSObject @property (class, nonatomic, readonly) FIRApp *defaultApp; - (void)configure; @end // ❌ Not exportable — Swift-generated stub, no actual interface // Only contains @class forward-declarations or typedef NS_ENUM wrappers // with no @interface / @protocol blocks ``` -------------------------------- ### Swift Package Manager Library Definition Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/exporting.md Example of a Swift Package Manager library definition. Inspect the direct target listed in the `targets:` array for Objective-C compatibility. ```swift // The product exposes "MySDK" — only inspect the MySDK target .library(name: "MySDK", targets: ["MySDK"]) // ✅ Direct target has publicHeadersPath — ObjC interface is reachable .target(name: "MySDK", publicHeadersPath: "Sources/Public", ...) // ❌ Only the internal dependency has headers — NOT reachable via the product .target(name: "MySDK", dependencies: ["MySDKInternal"], ...) // no publicHeadersPath .target(name: "MySDKInternal", publicHeadersPath: "Sources/Public", ...) // irrelevant ``` -------------------------------- ### Call Swift Throwing Methods from Kotlin Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/interoperability.md Demonstrates how to call Swift methods that throw errors from Kotlin using the `executeWithErrorHandling` utility. Includes examples for methods that always throw and methods with parameters and return values. ```kotlin import playground.Throw import playground.ThrowError val throwObj = Throw() // Method that always throws try { executeWithErrorHandling { errorPtr -> throwObj.justThrowAnError(errorPtr) } } catch (e: SwiftException) { println(e.nsError.localizedDescription) // ThrowError.error1 } // Method with param + return value val result: String? = try { executeWithErrorHandling { errorPtr -> throwObj.throwAnErrorWithParamAndReturn(param = "France", error = errorPtr) } } catch (e: SwiftException) { null } // result == "Hello France!" ``` -------------------------------- ### Simulator Management and Booting Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/migration.md Selects the first booted simulator or boots the first available iPhone simulator if none are running. It then prints the UDID of the simulator being used. ```bash UDID=$(xcrun simctl list devices booted | grep -m1 -oE '[0-9A-F]{8}-[0-9A-F-]{27}') if [ -z "$UDID" ]; then UDID=$(xcrun simctl list devices available | grep -m1 "iPhone " | grep -oE '[0-9A-F]{8}-[0-9A-F-]{27}') xcrun simctl boot "$UDID" fi echo "Using simulator: $UDID" ``` -------------------------------- ### Build and Launch macOS App Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/setup.md Build the KMP framework for macOS, then build the Xcode project and launch the application. Includes a 10-second delay for verification. ```bash ./gradlew ::linkDebugFrameworkMacosArm64 xcodebuild \ -project macosApp/macosApp.xcodeproj \ -scheme macosApp \ -configuration Debug \ -destination "platform=macOS" \ -derivedDataPath build/xcode \ build | tail -n 50 APP_PATH=$(find build/xcode/Build/Products -name "*.app" -not -path "*/PlugIns/*" | head -1) open "$APP_PATH" sleep 10 ``` -------------------------------- ### Build KMP Framework Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/migration.md Build the KMP framework end-to-end for a sanity check before proceeding with Xcode integration. ```bash ./gradlew :composeApp:linkDebugFrameworkIosSimulatorArm64 ``` -------------------------------- ### Update spmforkmp Skill Source: https://github.com/frankois944/skills/blob/main/README.md Updates the spmforkmp skill to the latest version if already installed. ```bash npx skills update spmforkmp ``` -------------------------------- ### Swift Class Not Suitable for Export Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/exporting.md Examples of Swift code constructs that are not directly exportable to Objective-C or Kotlin due to missing Objective-C compatibility annotations or visibility. ```swift // ❌ @objc inside a non-NSObject class — not bridgeable as-is @objc public class PureSwiftClass { // missing ": NSObject" ... } // ❌ @objc on an internal type — not visible to cinterop @objc class InternalHelper: NSObject { ... } // no `public` ``` -------------------------------- ### Load libswift_Concurrency.dylib During Tests Source: https://context7.com/frankois944/skills/llms.txt Configure `freeCompilerArgs` to specify the minimum OS version for `iosSimulatorArm64` binaries to ensure `libswift_Concurrency.dylib` is loaded correctly during tests. ```kotlin iosSimulatorArm64().binaries.getTest("debug").apply { freeCompilerArgs += listOf("-Xoverride-konan-properties=osVersionMin.ios_simulator_arm64=16.0") } ``` -------------------------------- ### Swift Class with @objcMembers Annotation Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/exporting.md Example of a Swift class annotated with `@objcMembers`, making its public members accessible to Objective-C and suitable for direct export to Kotlin. ```swift // ✅ exportToKotlin = true will work — public class exposed to ObjC @objcMembers public class MySDKClient: NSObject { public func connect(url: String) { ... } } // ✅ also valid — individual members annotated public class MySDKClient: NSObject { @objc public func connect(url: String) { ... } @objc public var isConnected: Bool = false } ``` -------------------------------- ### Apply SPM for KMP Plugin with Version Catalog Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/setup.md Configure your `libs.versions.toml` to manage the plugin version. Apply the plugin in your root and module `build.gradle.kts` files. ```toml [versions] spmForKmp = "1.9.1" # check Gradle Plugin Portal for latest [plugins] spmForKmp = { id = "io.github.frankois944.spmForKmp", version.ref = "spmForKmp" } ``` ```kotlin plugins { alias(libs.plugins.spmForKmp).apply(false) } ``` ```kotlin import io.github.frankois944.spmForKmp.swiftPackageConfig plugins { alias(libs.plugins.spmForKmp) } ``` -------------------------------- ### Build Xcode Project Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/migration.md Builds the Xcode project. The Gradle run-script phase is automatically triggered. This command targets a specific simulator and logs the last 50 lines of output. ```bash xcodebuild \ -project iosApp/iosApp.xcodeproj \ -scheme iosApp \ -configuration Debug \ -destination "platform=iOS Simulator,id=$UDID" \ -derivedDataPath build/xcode \ build 2>&1 | tail -n 50 ``` -------------------------------- ### Take Screenshot Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/migration.md Captures a screenshot of the simulator and saves it to the specified path. ```bash xcrun simctl io "$UDID" screenshot build/xcode/launch_verify.png ``` -------------------------------- ### Explicitly Include Binary Target in Swift Package Config Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/setup.md Demonstrates how to proactively include binary targets like XCFrameworks using `includeProduct` in the `swiftPackageConfig` to ensure they are correctly linked. ```kotlin target.swiftPackageConfig("nativeBridge") { exportedPackageSettings { includeProduct = listOf("GoogleMaps") // explicit: don't rely on auto-detection for binary targets } dependency { ... } } ``` -------------------------------- ### Include Remote Binary XCFramework (zip) Source: https://context7.com/frankois944/skills/llms.txt Integrate a pre-built binary .xcframework distributed as a zip archive from a URL. Ensure the checksum is correctly computed. ```kotlin target.swiftPackageConfig("nativeBridge") { minIos = "16.0" dependency { remoteBinary( url = uri("https://cdn.example.com/releases/VendorSDK-2.3.1.xcframework.zip"), checksum = "abc123def456...", // SHA-256 of the zip packageName = "VendorSDK", exportToKotlin = false ) } } ``` -------------------------------- ### Configure Kotlin Multiplatform Build for SPM Integration Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/bridge.md Configure your `build.gradle.kts` to apply the `spmForKmp` plugin and set up Swift package configurations for multiple targets. Ensure `ExperimentalForeignApi` is opted in. ```kotlin import io.github.frankois944.spmForKmp.swiftPackageConfig plugins { id("org.jetbrains.kotlin.multiplatform") id("io.github.frankois944.spmForKmp") version "1.9.1" } kotlin { listOf( iosArm64(), iosSimulatorArm64(), // add macosArm64(), tvosArm64(), watchosArm64() as needed ).forEach { target -> target.compilations.all { compileTaskProvider.configure { compilerOptions.optIn.add("kotlinx.cinterop.ExperimentalForeignApi") } } target.swiftPackageConfig("nativeBridge") { minIos = "16.0" // set your actual minimum // no dependency {} block — Apple SDK only } } } ``` -------------------------------- ### Configure Gradle for Swift Package Interoperability Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/interoperability.md Configure Gradle build scripts to enable advanced Swift interoperability features like Swift 6 async/await, typed throws, and improved C/ObjC interop. Also includes configuration for iOS simulator tests with specific OS versions. ```kotlin iosTarget.swiftPackageConfig(cinteropName = "playground") { toolsVersion = "6.0" // enables Swift 6 async/await + typed throws strictEnums = listOf("MyEnum", "MyError") // makes enums type-safe in Kotlin newPublicationInteroperability = true // experimental: better C/ObjC interop (Kotlin 2.3.20+) } // Required for async/await in iOS simulator tests: iosSimulatorArm64().binaries.getTest("debug").apply { freeCompilerArgs += listOf( "-Xoverride-konan-properties=osVersionMin.ios_simulator_arm64=16.0" ) } ``` -------------------------------- ### Add Local Swift Package Source: https://context7.com/frankois944/skills/llms.txt Reference a local Swift package located on disk, such as an internal library within the same repository. Ensure the `path` correctly points to the package directory. ```kotlin target.swiftPackageConfig("nativeBridge") { minIos = "16.0" dependency { localPackage( path = "${rootDir}/LocalPackages/MyAnalytics", packageName = "MyAnalytics", products = { add("MyAnalytics", exportToKotlin = false) }, ) } } ``` ```swift // src/swift/nativeBridge/AnalyticsBridge.swift import Foundation import MyAnalytics @objcMembers public class AnalyticsBridge: NSObject { public static func track(event: String) { MyAnalytics.track(event) } } ``` ```kotlin import nativeBridge.AnalyticsBridge AnalyticsBridge.track(event = "app_open") ``` -------------------------------- ### Swift Target Configurations for ObjC Compatibility Source: https://context7.com/frankois944/skills/llms.txt Illustrates Swift target configurations in `Package.swift` related to Objective-C compatibility. Use `publicHeadersPath` or `@objcMembers` for `exportToKotlin = true`; otherwise, bridging is required. ```swift // ✅ exportToKotlin = true — direct target has publicHeadersPath or @objcMembers .target(name: "FirebaseCore", publicHeadersPath: "Sources/Core/Public") @objcMembers public class MySDKClient: NSObject { public func connect(url: String) { ... } } // ❌ exportToKotlin = false — pure Swift, must bridge-wrap .target(name: "CryptoSwift", ...) // no publicHeadersPath, no @objc on public types // ❌ exportToKotlin = false — headers exist only on a transitive dep, not the direct target .library(name: "MySDK", targets: ["MySDK"]) .target(name: "MySDK", dependencies: ["MySDKInternal"]) // no publicHeadersPath here .target(name: "MySDKInternal", publicHeadersPath: "Sources/Public") // irrelevant ``` -------------------------------- ### Verify App Running Status Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/migration.md Waits for 10 seconds after launching the app and then checks if the app is still running by verifying its PID. Exits with an error if the app has crashed. ```bash sleep 10 if kill -0 "$APP_PID" 2>/dev/null; then echo "App is running — verification passed" else echo "App crashed after launch — verification FAILED" >&2 exit 1 fi ``` -------------------------------- ### Opt-in to ExperimentalForeignApi for Kotlin 2.x Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/setup.md Enable `ExperimentalForeignApi` for all compilations (main and test) in Gradle when using Kotlin 2.x. This avoids annotating every Kotlin file with `@OptIn`. ```kotlin listOf(iosArm64(), iosSimulatorArm64()).forEach { target.compilations.all { compileTaskProvider.configure { compilerOptions.optIn.add("kotlinx.cinterop.ExperimentalForeignApi") } } target.swiftPackageConfig("nativeBridge") { ... } } ``` -------------------------------- ### Build Targets Individually for SPM Issues Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/troubleshooting.md Build simulator and device targets separately to ensure each receives a clean fetch, resolving issues caused by incomplete artifact trees from previous builds. ```gradle ./gradlew ::SwiftPackageConfigAppleNativeBridgeCompileSwiftPackageIosSimulatorArm64 ``` ```gradle ./gradlew ::SwiftPackageConfigAppleNativeBridgeCompileSwiftPackageIosArm64 ``` -------------------------------- ### Include Local Binary XCFramework Source: https://context7.com/frankois944/skills/llms.txt Explicitly include a local binary .xcframework using `includeProduct`. This is useful when auto-detection fails, especially for vendor-supplied SDKs. ```kotlin target.swiftPackageConfig("nativeBridge") { minIos = "16.0" exportedPackageSettings { includeProduct = listOf("VendorSDK") // explicit: binary XCFrameworks often missed by auto-detection } dependency { localBinary( path = "${rootDir}/Frameworks/VendorSDK.xcframework", packageName = "VendorSDK", exportToKotlin = false // set true only after confirming ObjC headers via modulemap ) } } ``` -------------------------------- ### Configure Kotlin Experimental Foreign API Source: https://github.com/frankois944/skills/blob/main/spmforkmp/SKILL.md For Kotlin 2.x, opt into `ExperimentalForeignApi` at cinterop call sites. Add `target.compilerOptions.optIn.add("kotlinx.cinterop.ExperimentalForeignApi")` per target in Gradle, or annotate individual files with `@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)`. ```kotlin target.compilerOptions.optIn.add("kotlinx.cinterop.ExperimentalForeignApi") ``` ```kotlin @OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) ``` -------------------------------- ### Local Binary (XCFramework) Dependency Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/setup.md Use this when you have a pre-built `.xcframework` file on disk, typically provided by a vendor. Ensure the path to the XCFramework is correct. Set `exportToKotlin` to `true` only after confirming Objective-C headers via modulemap. ```kotlin import io.github.frankois944.spmForKmp.swiftPackageConfig kotlin { listOf(iosArm64(), iosSimulatorArm64()).forEach { target.swiftPackageConfig("nativeBridge") { minIos = "16.0" dependency { localBinary( path = "${rootDir}/Frameworks/VendorSDK.xcframework", packageName = "VendorSDK", exportToKotlin = false // set true only after confirming ObjC headers via modulemap ) } } } } ``` -------------------------------- ### Configure Minimum iOS Version for Swift Concurrency Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/troubleshooting.md Override the minimum iOS simulator version to 16.0 for the debug test binary to resolve 'libswift_Concurrency.dylib not loaded' errors when using async/await. ```kotlin kotlin { iosSimulatorArm64().binaries.getTest("debug").apply { freeCompilerArgs += listOf( "-Xoverride-konan-properties=osVersionMin.ios_simulator_arm64=16.0", ) } } ``` -------------------------------- ### Kotlin Usage of Swift Basic Class Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/interoperability.md Instantiate and interact with a Swift class from Kotlin, calling its methods and accessing its properties. Demonstrates handling custom initializers, properties, static and instance methods, name overrides, and optional parameters. ```kotlin import playground.Basic // Custom initializer val obj = Basic(someValue = 42) // Properties val readOnly = obj.readOnlyProperty() obj.setMutableProperty(10) // Instance, static, class methods obj.method() Basic.staticMethod() Basic.classMethod() // @objc name override — called by the Obj-C name, not the Swift name obj.methodWithNameOverride() // Parameterized method val greeting = obj.method(param = "World") // → "Hello World!" // NSNumber obj.methodNumber(param = NSNumber(42)) // Optional — pass null for nil obj.methodOptional(param = null) ``` -------------------------------- ### Update Build Settings for SPM Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/migration.md Configure the 'Other Linker Flags', 'Framework Search Paths', and 'Enable User Script Sandboxing' build settings. Ensure 'Enable User Script Sandboxing' is set to 'NO' for the Gradle run script phase to allow network and write access. ```text Other Linker Flags: -framework shared Framework Search Paths: $(SRCROOT)/../build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME) Enable User Script Sandboxing: NO ``` -------------------------------- ### Direct Import of ObjC-Exported Products Source: https://context7.com/frankois944/skills/llms.txt Import and use Objective-C exported products directly in Kotlin. No bridge wrapper is needed for these products. ```kotlin // Kotlin — ObjC-exported products import directly (no bridge wrapper needed) import FirebaseCore.FIRApp import FirebaseAnalytics.FIRAnalytics @ExperimentalForeignApi FIRApp.configure() ``` -------------------------------- ### Create Swift Bridge Folder Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/migration.md Create the `src/swift/[cinteropName]/` folder structure. If you had existing Swift bridge code, move it to this new location. For ObjC-compatible packages, the bridge file can be empty. ```bash mkdir -p shared/src/swift/nativeBridge ``` -------------------------------- ### Configure Swift Package for Multiple Platform Families (iOS + macOS) Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/setup.md Configure Swift Package Manager settings for different platform families, such as iOS and macOS, using separate `swiftPackageConfig` blocks. This allows for platform-specific configurations like minimum deployment versions. ```kotlin import io.github.frankois944.spmForKmp.swiftPackageConfig kotlin { listOf(iosArm64(), iosSimulatorArm64()).forEach { target.swiftPackageConfig("nativeIosBridge") { minIos = "16.0" } } macosArm64 { swiftPackageConfig("macosBridge") { // separate bridge for macOS } } } ``` -------------------------------- ### Configure Package Dependency Prefix Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/migration.md To restore the 'cocoapods.' prefix during the transition, use the `packageDependencyPrefix` setting within the `swiftPackageConfig` block. This avoids modifying all import sites at once. ```kotlin target.swiftPackageConfig("nativeBridge") { packageDependencyPrefix = "cocoapods" } ``` -------------------------------- ### Remote Binary (XCFramework zip) Dependency Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/setup.md Use this when a vendor distributes a pre-built `.xcframework` as a downloadable zip file. Provide the correct URL and the SHA-256 checksum of the zip file. The `packageName` is required. ```kotlin import io.github.frankois944.spmForKmp.swiftPackageConfig kotlin { listOf(iosArm64(), iosSimulatorArm64()).forEach { target.swiftPackageConfig("nativeBridge") { minIos = "16.0" dependency { remoteBinary( url = uri("https://cdn.example.com/releases/VendorSDK-2.3.1.xcframework.zip"), checksum = "abc123def456...", // SHA-256 of the zip file packageName = "VendorSDK", exportToKotlin = false ) } } } } ``` -------------------------------- ### Annotate Individual Call Sites with ExperimentalForeignApi Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/setup.md Alternatively to enabling `ExperimentalForeignApi` for all compilations, annotate individual call sites with `@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)` when using Kotlin 2.x. ```kotlin @OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) actual fun platformInfo(): String = MyBridge().appVersion() ``` -------------------------------- ### Import Swift Package Config in Gradle Source: https://github.com/frankois944/skills/blob/main/spmforkmp/SKILL.md Import `io.github.frankois944.spmForKmp.swiftPackageConfig` in `build.gradle.kts`. Without it, Gradle's built-in function is used, leading to signature mismatches. Call it as `target.swiftPackageConfig("bridgeName") { ... }`. ```kotlin import io.github.frankois944.spmForKmp.swiftPackageConfig ``` -------------------------------- ### Add Run Script Build Phase Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/migration.md Add a new Run Script build phase in Xcode and drag it to the top. Adjust the `cd` path to match your project structure, ensuring it reaches the directory containing `gradlew`. ```bash cd "$SRCROOT/../" ./gradlew :shared:embedAndSignAppleFrameworkForXcode ``` -------------------------------- ### Configure spmForKmp Plugin in Gradle Source: https://context7.com/frankois944/skills/llms.txt Set up the spmForKmp plugin in your Gradle build files. Ensure Kotlin Multiplatform settings and Swift bridge configurations are correctly applied. ```toml # libs.versions.toml [versions] spmForKmp = "1.9.1" # always verify latest at plugins.gradle.org/plugin/io.github.frankois944.spmForKmp [plugins] spmForKmp = { id = "io.github.frankois944.spmForKmp", version.ref = "spmForKmp" } ``` ```properties # gradle.properties kotlin.mpp.enableCInteropCommonization=true ``` ```kotlin // Root build.gradle.kts plugins { alias(libs.plugins.spmForKmp).apply(false) } // Module build.gradle.kts import io.github.frankois944.spmForKmp.swiftPackageConfig plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.spmForKmp) } kotlin { listOf( iosArm64(), iosSimulatorArm64(), ).forEach { target -> target.compilations.all { compileTaskProvider.configure { compilerOptions.optIn.add("kotlinx.cinterop.ExperimentalForeignApi") } } target.swiftPackageConfig("nativeBridge") { minIos = "16.0" // spmWorkingPath = "${projectDir}/SPM" // persist across ./gradlew clean } } } ``` -------------------------------- ### Verification Flow Script Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/migration.md Execute this script at the project root to verify the migration. It ensures that build exit codes are propagated through pipes and stops execution on the first non-zero exit code. ```bash set -o pipefail # propagate xcodebuild exit code through the pipe ``` -------------------------------- ### Configure Single Target for SPM Integration Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/bridge.md Configure a single target for SPM integration, including compiler options and Swift package configuration. This is an alternative to looping through multiple targets. ```kotlin import io.github.frankois944.spmForKmp.swiftPackageConfig iosSimulatorArm64 { compilerOptions.optIn.add("kotlinx.cinterop.ExperimentalForeignApi") swiftPackageConfig("iosBridge") { minIos = "16.0" } } ``` -------------------------------- ### Setting Package Dependency Prefix Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/exporting.md Configure a prefix for Swift package dependencies to avoid naming collisions or to manage imports when migrating from CocoaPods. ```kotlin target.swiftPackageConfig("nativeBridge") { packageDependencyPrefix = "spm" } ``` -------------------------------- ### Enable Detailed Tracing for Debugging Source: https://context7.com/frankois944/skills/llms.txt Enable detailed tracing for debugging by setting `spmforkmp.enableTracing=true` in `gradle.properties`. Traces will be written to the `spmForKmpTrace/` directory in the project root. ```properties # Enable detailed tracing for all other issues # gradle.properties: # spmforkmp.enableTracing=true # Traces written to spmForKmpTrace/ in the project root ``` -------------------------------- ### Configure Swift Package Cinterop Settings Source: https://context7.com/frankois944/skills/llms.txt Configure Cinterop settings for a Swift package, including enum mapping, exception handling mode, linker flags, and package dependency prefixes. ```kotlin target.swiftPackageConfig("nativeBridge") { strictEnums = listOf("FIRLoggerLevel", "MyErrorEnum") // enum → type-safe Kotlin enum class foreignExceptionMode = "objc-wrap" // ObjC exceptions → ForeignException linkerOpts = listOf("-framework", "Security") packageDependencyPrefix = "spm" // import spm.FirebaseCore.FIRApp toolsVersion = "6.0" // enables Swift 6 async/await newPublicationInteroperability = true // experimental: better C/ObjC interop (Kotlin 2.3.20+) debug = true // debug symbols during dev } ``` -------------------------------- ### Create Swift Bridge for Pure Swift Packages Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/troubleshooting.md Wrap pure Swift packages with an Objective-C compatible interface using `@objcMembers` to make them visible in Kotlin. ```swift // src/swift/nativeBridge/MyWrapper.swift import Foundation import ThePureSwiftPackage @objcMembers public class MyWrapper: NSObject { public func doThing(input: String) -> String { return ThePureSwiftPackage.doThing(input) } } ``` -------------------------------- ### Mix Local and Remote Dependencies Source: https://context7.com/frankois944/skills/llms.txt Combine local binary .xcframeworks and remote Swift packages within a single `dependency {}` block. This allows for flexible dependency management. ```kotlin import io.github.frankois944.spmForKmp.swiftPackageConfig target.swiftPackageConfig("nativeBridge") { minIos = "16.0" dependency { remotePackageVersion( url = uri("https://github.com/firebase/firebase-ios-sdk.git"), version = "11.8.0", products = { add("FirebaseCore", exportToKotlin = true) // verified: has ObjC headers add("FirebaseAnalytics", exportToKotlin = true) // verified: has ObjC headers }, ) remotePackageVersion( url = uri("https://github.com/krzyzanowskim/CryptoSwift.git"), version = "1.8.4", products = { add("CryptoSwift", exportToKotlin = false) // pure Swift → bridge only }, ) localBinary( path = "${rootDir}/Frameworks/VendorSDK.xcframework", packageName = "VendorSDK", exportToKotlin = false ) } } ``` -------------------------------- ### Export Swift Package Settings Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/setup.md Configure exported Swift package settings for a native bridge dependency. Ensure FirebaseCore and KeychainAccess are included. ```kotlin target.swiftPackageConfig("nativeBridge") { exportedPackageSettings { includeProduct = listOf("FirebaseCore", "KeychainAccess") } dependency { ... } } ``` -------------------------------- ### Local Package (Source) Dependency Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/setup.md Use this when your Swift package is located on disk, such as an internal library within the same repository or a sibling directory. Specify the correct local path and package name. ```kotlin import io.github.frankois944.spmForKmp.swiftPackageConfig kotlin { listOf(iosArm64(), iosSimulatorArm64()).forEach { target.swiftPackageConfig("nativeBridge") { minIos = "16.0" dependency { localPackage( path = "${rootDir}/LocalPackages/MyAnalytics", packageName = "MyAnalytics", products = { add("MyAnalytics", exportToKotlin = false) }, ) } } } } ``` -------------------------------- ### Configure Swift Package for Multiple Targets Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/setup.md Use the `swiftPackageConfig` block within a loop to configure Swift Package Manager settings for multiple targets, such as iOS variants. This is the recommended pattern for sharing a common bridge folder. ```kotlin import io.github.frankois944.spmForKmp.swiftPackageConfig kotlin { listOf( iosArm64(), iosSimulatorArm64(), // add macosArm64(), tvosArm64(), etc. as needed ).forEach { target -> target.swiftPackageConfig("nativeBridge") { // creates src/swift/nativeBridge/ // all targets share the same bridge folder and config } } } ``` -------------------------------- ### Grep for @objc Annotations in Swift Source Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/exporting.md Command to search Swift source files for `@objc` or `@objcMembers` annotations, indicating potential Objective-C compatibility. ```bash grep -rn "@objc\|@objcMembers" Sources/ ``` -------------------------------- ### Locate App Path and Bundle ID Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/migration.md Finds the path to the built .app file and extracts its bundle identifier from the Info.plist file. ```bash APP_PATH=$(find build/xcode/Build/Products/Debug-iphonesimulator -maxdepth 1 -name "*.app" | head -1) BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$APP_PATH/Info.plist") ``` -------------------------------- ### Access Bundled Resources in Swift Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/bridge.md Access bundled resources like images, JSON, or fonts in Swift using `Bundle.module.url(forResource:withExtension:)`. Resources can be placed in `Resources-process/`, `Resources-copy/`, or `Resources-embed/` folders within your bridge directory. ```swift let url = Bundle.module.url(forResource: "config", withExtension: "json") ``` -------------------------------- ### Create Swift Bridge for Apple SDK APIs Source: https://context7.com/frankois944/skills/llms.txt Expose Apple SDK APIs like UIKit and Foundation to Kotlin using a Swift bridge class. Ensure all cross-platform elements use `@objcMembers public class ... : NSObject`. ```swift // src/swift/nativeBridge/MyBridge.swift import Foundation import UIKit @objcMembers public class MyBridge: NSObject { public func appVersion() -> String { return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown" } // Return NSObject and cast in Kotlin for reliable platform type mapping public func makeLabel(text: String) -> NSObject { let label = UILabel() label.text = text return label } public static func currentLocale() -> String { return Locale.current.identifier } } ``` ```kotlin // Kotlin usage import nativeBridge.MyBridge import platform.UIKit.UILabel val version: String = MyBridge().appVersion() val locale: String = MyBridge.currentLocale() val label = MyBridge().makeLabel(text = "Hello") as UILabel ``` -------------------------------- ### Swift Package Configuration for Kotlin Export Source: https://github.com/frankois944/skills/blob/main/spmforkmp/references/exporting.md Configure a Swift package to be exported to Kotlin, specifying remote package details and enabling export for specific products. Ensure the product has Objective-C headers for successful export. ```kotlin target.swiftPackageConfig("nativeBridge") { dependency { remotePackageVersion( url = uri("https://github.com/firebase/firebase-ios-sdk.git"), version = "11.8.0", products = { add("FirebaseAnalytics", exportToKotlin = true) // verified: has ObjC headers add("FirebaseCore", exportToKotlin = true) // verified: has ObjC headers add("FirebaseMessaging", exportToKotlin = true) // verified: has ObjC headers }, ) } } ``` -------------------------------- ### Swift API Wrapper for VendorSDK Source: https://context7.com/frankois944/skills/llms.txt A pure Swift API wrapper for VendorSDK, only necessary if you need to interact with the SDK's functionality directly from Swift code. ```swift // src/swift/nativeBridge/VendorBridge.swift — only needed if pure Swift API import Foundation import VendorSDK @objcMembers public class VendorBridge: NSObject { public static func initialize(apiKey: String) { VendorSDK.setup(apiKey: apiKey) } } ``` -------------------------------- ### Add Remote SPM Package by Version Source: https://context7.com/frankois944/skills/llms.txt Configure a remote Swift package dependency using a specific version tag. Ensure the package's `.platforms` in `Package.swift` are compatible and check for binary XCFramework targets. ```kotlin import io.github.frankois944.spmForKmp.swiftPackageConfig kotlin { listOf(iosArm64(), iosSimulatorArm64()).forEach { target -> target.compilations.all { compileTaskProvider.configure { compilerOptions.optIn.add("kotlinx.cinterop.ExperimentalForeignApi") } } target.swiftPackageConfig("nativeBridge") { minIos = "16.0" dependency { remotePackageVersion( url = uri("https://github.com/krzyzanowskim/CryptoSwift.git"), version = "1.8.4", products = { add("CryptoSwift", exportToKotlin = false) // pure Swift → bridge wrapper required }, ) } } } } ``` ```swift // src/swift/nativeBridge/CryptoSwiftBridge.swift import Foundation import CryptoSwift @objcMembers public class CryptoSwiftBridge: NSObject { public func md5(of value: String) -> String { return value.md5() } public func sha256(of value: String) -> String { return value.sha256() } } ``` ```kotlin import nativeBridge.CryptoSwiftBridge val hash = CryptoSwiftBridge().md5(of = "hello") ``` -------------------------------- ### Usage of Swift Error Handling Helper in Kotlin Source: https://context7.com/frankois944/skills/llms.txt Demonstrates how to use the `executeWithErrorHandling` helper to call Swift functions that throw errors and catch the resulting `SwiftException` in Kotlin. Handles both functions with no return value and functions with a return value. ```kotlin // Usage import nativeBridge.Throw val throwObj = Throw() try { executeWithErrorHandling { errorPtr -> throwObj.justThrowAnError(errorPtr) } } catch (e: SwiftException) { println(e.nsError.localizedDescription) // "error1" } val result: String? = try { executeWithErrorHandling { errorPtr -> throwObj.throwAnErrorWithParamAndReturn(param = "France", error = errorPtr) } } catch (e: SwiftException) { null } // result == "Hello France!" ```