Using expo-modules with 3rd party local xcframeworks (iOS)

I’m trying to build a react-native wrapper module for the official Spotify SDK, using expo-modules. I’ve added the SDK in my .podspec file using s.vendored_frameworks. Example app’s project pre-build works fine, but the framework’s header files do not get copied, and while building the app for the Simulator, I get this error:

❌  (/Users/<username>/Library/Developer/Xcode/DerivedData/expospotifyexample-ayfjxgjzbhoakacqvfpiaawhvpwz/Build/Products/Debug-iphonesimulator/ExpoSpotify/ExpoSpotify-umbrella.h:13:9)

  11 | #endif
  12 | 
> 13 | #import "SpotifyAppRemote.h"
     |         ^ 'SpotifyAppRemote.h' file not found
  14 | #import "SpotifyiOS.h"
  15 | #import "SPTAppRemote.h"
  16 | #import "SPTAppRemoteAlbum.h"

It works fine if I manually copy SDK’s header files to the path from the error.

my folder structure (just the important bits):

├─ example/
│  ├─ ios/
│  │  ├─ Pods/
│  │  │  ├─ Headers/
│  │  │  │  ├─ Public/
│  │  │  │  │  ├─ ExpoSpotify/
│  │  │  │  │  │  ├─ ExpoSpotify.modulemap
│  │  │  │  │  │  ├─ ExpoSpotify-umbrella.h
├─ ios/
│  ├─ SpotifyiOS.xcframework/
│  │  ├─ ios-arm64_armv7/
│  │  │  ├─ SpotifyiOS.framework/
│  │  │  │  ├─ Headers/
│  │  │  │  │  ├─ SpotifyAppRemote.h
│  │  │  │  ├─ Modules/
│  │  │  │  │  ├─ module.modulemap
│  │  ├─ ios-arm64_i386_x86_64-simulator/
│  │  │  ├─ SpotifyiOS.framework/
│  │  │  │  ├─ Headers/
│  │  │  │  │  ├─ SpotifyAppRemote.h
│  │  │  │  ├─ Modules/
│  │  │  │  │  ├─ module.modulemap
│  │  ├─ Info.plist
│  ├─ ExpoSpotify.podspec
│  ├─ ExpoSpotifyModule.swift

my .podspec file:

require 'json'

package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))

Pod::Spec.new do |s|
  s.name           = 'ExpoSpotify'
  s.version        = package['version']
  s.summary        = package['description']
  s.description    = package['description']
  s.license        = package['license']
  s.author         = package['author']
  s.homepage       = package['homepage']
  s.platform       = :ios, '13.0'
  s.swift_version  = '5.4'
  s.source         = { git: 'https://github.com/' }
  s.static_framework = true

  s.dependency 'ExpoModulesCore'

  # Swift/Objective-C compatibility
  s.pod_target_xcconfig = {
    'DEFINES_MODULE' => 'YES',
    'SWIFT_COMPILATION_MODE' => 'wholemodule',
  }

  s.preserve_paths = "ExpoSpotify/SpotifyiOS.xcframework/**/*"
  s.vendored_frameworks = 'ExpoSpotify/SpotifyiOS.xcframework'
  s.source_files = "**/*.{h,m,swift}", 'ExpoSpotify/SpotifyiOS.xcframework/**/Headers/*.{h,m}'
end

One thing I’ve noticed. In the example apps example/ios/Pods/Headers/Public/ExpoSpotify-umbrella.h file, the imports are doubled and flat:

#import "SpotifyAppRemote.h"
... other headers
#import "SpotifyAppRemote.h"
... other headers

but when I add to the .podspec the path to framework’s modulemap:

s.header_mappings_dir = 'ExpoSpotify/ios/SpotifyiOS.xcframework/**/Modules'

then imports are relative (but wrong path)

#import "../../../../../SpotifyiOS.xcframework/ios-arm64_armv7/SpotifyiOS.framework/Headers/SpotifyAppRemote.h"
#import "../../../../../SpotifyiOS.xcframework/ios-arm64_i386_x86_64-simulator/SpotifyiOS.framework/Headers/SpotifyAppRemote.h"

if it ditch the ExpoSpotify in the path

s.preserve_paths = "SpotifyiOS.xcframework/**/*"
  s.vendored_frameworks = 'SpotifyiOS.xcframework'
  s.source_files = "**/*.{h,m,swift}", 'SpotifyiOS.xcframework/**/Headers/*.{h,m}'
  s.header_mappings_dir = 'SpotifyiOS.xcframework/**/Modules'

The import path is shorter (still wrong):

#import "../../ios-arm64_armv7/SpotifyiOS.framework/Headers/SpotifyAppRemote.h"
#import "../../ios-arm64_i386_x86_64-simulator/SpotifyiOS.framework/Headers/SpotifyAppRemote.h"

I’m using MacBook with M2 processor, macOS Ventura (13.3.1), latest Xcode, Cocoapods 1.12.0.

Hi @jakub.jankowski

If you don’t get an answer here, try asking on the creating-expo-modules channel on Discord.

1 Like

hey, did you ever figure this out? trying to solve something similar!

Hey, I did not. Tried StackOverflow and the Discord channel mentioned above with no luck. Haven’t had a lot of time since to nail it myself, as it’s my pleasure project, so let me know if you do.

Hi

I don’t really know much about this stuff, but given the folder structure, shouldn’t you have the following?

s.preserve_paths = "ios/SpotifyiOS.xcframework/**/*"
s.vendored_frameworks = 'ios/SpotifyiOS.xcframework'
s.source_files = "**/*.{h,m,swift}", 'ios/SpotifyiOS.xcframework/**/Headers/*.{h,m}'

Also, I think you might need to remove the header_mappings_dir from the podspec file. Apparently when using vendored_flameworks, CocoaPods should automatically handle the linking of the headers.

Maybe try making those changes, clean the project (delete derived data) and re-run pod install.

Hi! I am new to native dev as well. Are you saying that that should in the Podfile of my main project? My plan was to drag the Spotify ios-sdk into the workspace of the example project, create a bridging header, and then debug from there. I am a bit confused by the project structure frankly since the “main” project itself doesn’t have an xcode workspace.

I have been following along with Spotify’s ReadMe on the SDK repo. When I add a bridging header for the SpotifyiOS.h, however, I just get errors that:

":x: (ios/Spotify-Bridging-Header.h:1:9)

1 | #import <SpotifyiOS/SpotifyiOS.h>
| ^ ‘SpotifyiOS/SpotifyiOS.h’ file not found
2 |

:x: error: failed to emit precompiled header ‘/Users/griffinbaker/Library/Developer/Xcode/DerivedData/expospotifyexample-fowwbnxxqxizsharlcbfuiaflrkt/Build/Intermediates.noindex/PrecompiledHeaders/Spotify-Bridging-Header-swift_3J2Z0WOY1WYXZ-clang_2FNIRI3T0YKXU.pch’ for bridging header /Users/griffinbaker/Desktop/expo-spotify/e"

Link to sdk:

No. In theory you don’t need a Podfile at all. It should be possible to do this in a Managed Expo app (with no ios directory).

Start like this:

npx create-expo-app spotify-test
cd spotify-test
npx create-expo-module --local

That will create the module skeleton in modules/your-module/...

The YourModule.podspec under modules/your-module/ios is where that sort of stuff would need to be added.

But further than that I am not really sure.

See Expo Modules API: Overview

Ok, thanks. I did get everything to compile but when trying to use within my actual module it was throwing errors about values from the spotify sdk not being in scope.

If my project compiles, and this is the swift package file for the spotify sdk:

// swift-tools-version:5.5
import PackageDescription

let package = Package(
name: “SpotifyiOS”,
platforms: [
.iOS(.v9)
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: “SpotifyiOS”,
targets: [“SpotifyiOS”])
],
dependencies: ,
targets: [
.binaryTarget(
name: “SpotifyiOS”,
path: “SpotifyiOS.xcframework”
)
]
)

shouldn’t I just be able to import SpotifyiOS in my swift module file? what am I missing?

I too am facing the same problem. However, if I remove the imported headers in the umbrella.h file, I’m able to compile. Is approach even correct?

Or is the obj-c based frameworks are not supported in expo modules?

I think you might have more luck asking this on the Discord creating-expo-modules channel