How to manually add a .xcframework to a Flutter iOS Plugin?
Asked Answered
R

4

18

I'm trying to create a Flutter Plugin to use a native library. This library I'm trying to use is stored in a private repository and can be used with Swift Dependency Manager.

This is causing me a headache, cause I can't add a private repository dependency in my plugin (I couldn't find a way to do this in .podspec file), so what I've done:

  1. I've added the plugin to the Example project with Swift Package Manager
  2. Manually copied MyDependency.xcframework folder to MyPlugin/ios folder
  3. Referenced it in podspec file, like this:
s.preserve_paths = 'MyDependency.xcframework'
s.xcconfig = { 'OTHER_LDFLAGS' => '-framework MyDependency' }
s.vendored_frameworks = 'MyDependency.xcframework'

Doing this I'm able to use MyDependency inside plugin's sources.

My current problem is: This is only working in Simulator.

Before doing this, the project was running without any problem in real devices.

This is the error message I'm receiving every time I tried to run in a real device: enter image description here

Also, I've made a test using the dependency directly from Swift Dependency Manager and is working fine. I think the problem is the way I'm adding the framework to my plugin.

enter image description here

Riband answered 16/9, 2021 at 20:11 Comment(8)
Have you successfully built an app on an iOS physical device without .xcframework? Also, you don't need all 3 steps you added (cocoapods, SPM and xcframework). Just drag .xcframework into your project.Nous
Yeah, without the .xcframework it's working fine. I don't wan't to drag the .xcframework directly to my project. I need to create a plugin that can be used in other projects that we have here. Also, I (think) don't have directly access to the .xcframework. I do these 3 steps just to get the files downloaded by SPM.Riband
So what is your desired distribution strategy for that closed source code? I don't understand how you can use SPM if its closed source.Nous
That first screenshot happens when you haven't set up your project/ device correctly. Your device needs to be automatically added to your provisioning profile. I presume you have the Apple Developer Program membership and selected the correct team under signing and capabilities?Nous
The second screenshot (showing SPM in your example project Runner) is just your library, it may/ may not have the .xcframework. Also, when a user adds your plugin to their project, your plugin gets added via cocoapods, not SPM. When you block out names, you make things harder to see 😂. SPM is not applicable for you, IMHO.Nous
I think you don't understand the point here... I'm creating a Flutter plugin, that will be used in our projects. It's just a wrapper that contains an Android/iOS SDK and provide some methods to interact with them. The iOS SDK is provided in a private repo. As I can't add a private repository to a podspec file, my alternative was to add this dependency in other project with SPM and manually copied the .xcframework to my plugin folder. So I've just used my Flutter Plugin in my main Flutter project. The problem is after that, I can't be able to run in a real iOS device anymore...Riband
my alternative was to add this dependency in other project with SPM and manually copied the .xcframework to my plugin folder It's really not clear why you're using SPM if you're going to drag the XCF into the plugin folder. Both are bad ideas though, for Flutter plugins. You should configure XCF in Cocoapods using vendored_frameworks. It's not a well documented feature though: this issue has an example project. I've never used itNous
One reason why SPM is bad in Flutter is installing a package in Flutter is not going to add Package.resolved in the Xcodeproj. It only changes the dependencies on the Podfile/ output of pod install.Nous
R
1

After doing some research, I've found some links giving me an ideia about the real problem...

To solve this, I've added a simple script to my main project's build process.

This script adds the code signing to inner .framework files.

cd "${CODESIGNING_FOLDER_PATH}/Frameworks/"

# flatten nested frameworks by copying to APP.app/Frameworks
for framework in *; do
    if [ -d "$framework" ]; then
        if [ -d "${framework}/Frameworks" ]; then
            echo "Moving embedded frameworks from ${framework} to ${PRODUCT_NAME}.app/Frameworks"
            cp -R "${framework}/Frameworks/" .
            rm -rf "${framework}/Frameworks"
        fi
    fi
done

# remove any leftover nested frameworks (i.e. 'PackageName_359AFEED79E48935_PackageProduct.framework')
for framework in *; do
    if [ -d "$framework" ]; then
        if [ -d "${framework}/Frameworks" ]; then
            echo "Removing embedded frameworks from ${framework} to ${PRODUCT_NAME}.app/Frameworks"
            rm -rf "${framework}/Frameworks"
        fi
    fi
done

# codesign for Debugging on device
if [ "${CONFIGURATION}" == "Debug" ] & [ "${SDKROOT}" != *Simulator* ] ; then

    echo "Code signing frameworks..."
    find "${CODESIGNING_FOLDER_PATH}/Frameworks" -maxdepth 1 -name '*.framework' -print0 | while read -d $'\0' framework
    do
        # only sign frameworks without a signature
        if ! codesign -v "${framework}"; then
            codesign --force --sign "${EXPANDED_CODE_SIGN_IDENTITY}" --preserve-metadata=identifier,entitlements --timestamp=none "${framework}"
            echo "Added missing signature to '${framework}'"
        fi
    done
fi
Riband answered 21/9, 2021 at 12:44 Comment(2)
Where do you add (and call) this script?Liggins
This script is part of the Podfile in the main projectRiband
P
24

I got mine working by adding the following lines to the xxx.podspec:

s.preserve_paths = 'xxxxxx.xcframework/**/*'
s.xcconfig = { 'OTHER_LDFLAGS' => '-framework xxxxxx' }
s.vendored_frameworks = 'xxxxxx.xcframework'

Notice the /**/* at the end of s.preserve_paths

Here is the full xxx.podspec file:

#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint xxx.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
  s.name             = 'xxx'
  s.version          = '1.0.0'
  s.summary          = 'xxx'
  s.description      = <<-DESC
xxx is a flutter plugin
                       DESC
  s.homepage         = 'https://example.com'
  s.license          = { :file => '../LICENSE', :type => 'BSD' }
  s.author           = { 'xxx' => '[email protected]' }
  s.source           = { :path => '.' }
  s.source_files = 'Classes/**/*'
  s.dependency 'Flutter'
  s.platform = :ios, '10.0'

  # Flutter.framework does not contain a i386 slice.
  s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
  s.swift_version = '5.0'

  s.preserve_paths = 'xxxxxx.xcframework/**/*'
  s.xcconfig = { 'OTHER_LDFLAGS' => '-framework xxxxxx' }
  s.vendored_frameworks = 'xxxxxx.xcframework'
  
  # Inorder to add multiple frameworks
  s.preserve_paths = ['xxxxxxx.xcframework/**/*', 'yyyyyyy.xcframework/**/*']
  s.xcconfig = { 'OTHER_LDFLAGS' => ['-framework xxxxxxx', '-framework yyyyyyy'] }
  s.vendored_frameworks = ['xxxxxxx.xcframework', 'yyyyyyy.xcframework']      

end

Make sure to copy the xxxxxx.xcframework directory to .../xxx/ios/xxxxxx.xcframework (where your plugin xxx.podspec is located)


Add xxxxxx.xcframework to Pods scheme to be able to import it

The Frameworks directory should be below the Pods scheme when running the flutter command to create a plugin ie.

flutter create --org com.xxx --template=plugin --platforms=android,ios -a java -i swift xxx

Source: Developing packages & plugins

(If not, add it)

Adding framework 1

  1. Navigate to Pods scheme and right-click on Frameworks
  2. Click Add Files to "Pods"...

enter image description here

  1. Navigate to the root of the ios directory where the xxxxxx.xcframework is located
  2. Uncheck Copy items if needed
  3. Select Create folder references
  4. Check Pods-Runner
  5. Check your plugin ie. xxx
  6. Click Add

Embed xxxxxx.xcframework into your project

(this should allow you to run/publish to real devices)

enter image description here

  1. Click on the Pods scheme
  2. In TARGETS select Pods-Runner
  3. Click on the General tab
  4. Ensure the xxxxx.xcframework is present underneath Frameworks and Libraries (if not, add it by clicking on the +)
  5. Change Embed to Embed & Sign

enter image description here

  1. Click on your plugin ie. xxx
  2. Ensure the xxxxx.xcframework is present underneath Frameworks and Libraries
  3. Change Embed to Embed & Sign

enter image description here

  1. Again, for both targets below Pods scheme, the next steps should already be fine, but just check it
  2. Click Build Phases
  3. Click Link Binary With Libraries
  4. Ensure xxxxx.xcframework is present, if not add it by clicking the + and that the Status is set to Required

Now you should be able to import the framework and reference it in your code and should also publish perfectly fine.

Hope this helps, and good luck. I hope this doesn't change again soon

fingers crossed

Pronghorn answered 3/12, 2021 at 5:46 Comment(14)
Thanks for publishing your solution!! I was reading and had a doubt, maybe you can explain this... I've noticed you manually added your framework file to Pods... In the Flutter default .gitignore file, this folder is listed to not be versioned... This will really work for other developers as well or it's a local solution?Riband
@Riband It will work, as long as you embed (step 8) the xcframework.Pronghorn
@Pronghorn what role does the pubspec file play and do I have to run a cocoapods command? Also what if I have multiple xcframework's, how do I reference multiple frameworks in the pubspec file?Fibriform
@Pronghorn sorry I meant podspec not pubspec :) Should I duplicate the lines so that I have multiple s.preserve_paths lines, or something like this: s.preserve_paths = 'X1.xcframework/**/*, X2.xcframework/**/*' s.xcconfig = { 'OTHER_LDFLAGS' => '-framework WebRTC -framework X1 -framework X2' } s.vendored_frameworks = 'X1.xcframework, X2.xcframework'Fibriform
@Fibriform To my mind separate entries will work, but if you can merge it it should be fine as well. I haven't tested it and can't at this point. Maybe if you know, update this answer with the solution for multiple xcframeworksPronghorn
@Pronghorn What if i have .a file? Its an Objective-C static library. Is the process the same? Thank you for any help!Promethean
@Promethean I have never done that before, but it seems possible - have a look at this post #50442263Pronghorn
Is there a way to automate Add xcframework to Pods scheme?Wicks
@Wicks What do you mean with "automate"? I'm not sure there is a script or something you can use. Because it is XCode - you have to use the UI in order for everything to work.Pronghorn
@Pierre, Whenever I run the Flutter application from Android Studio, pod install gets executed and the changes we made in Pods project scheme gets lost and I have to do it again.Wicks
@Wicks That doesn't sound right. Are you running Android Studio on a mac? The above describes how to include the framework in your Flutter Plugin you build. Then your plugin is referenced by your Flutter project. You shouldn't do this directly in your Flutter project.Pronghorn
@Pierre, Yeah I am running Android Studio on a mac. I followed the exact steps from your answer to add XCFramework into my Flutter Plugin. Then if I run the example application directly from Xcode it is working as expected but if I run from the Android Studio internally it executes pod install and the configuration we made in Xcode is lost, so I have to again add the XCFramework under Framework and Libraries section then again it works from Xcode.Wicks
@Wicks I'm having the same issue, all the configuration is lost when I run my app from Android Studio as it runs pod install. Did you find a solution to this?Realist
@Realist Instead of running, I'm doing Debugging (⌥⌘R) which does not runs pod installWicks
R
1

After doing some research, I've found some links giving me an ideia about the real problem...

To solve this, I've added a simple script to my main project's build process.

This script adds the code signing to inner .framework files.

cd "${CODESIGNING_FOLDER_PATH}/Frameworks/"

# flatten nested frameworks by copying to APP.app/Frameworks
for framework in *; do
    if [ -d "$framework" ]; then
        if [ -d "${framework}/Frameworks" ]; then
            echo "Moving embedded frameworks from ${framework} to ${PRODUCT_NAME}.app/Frameworks"
            cp -R "${framework}/Frameworks/" .
            rm -rf "${framework}/Frameworks"
        fi
    fi
done

# remove any leftover nested frameworks (i.e. 'PackageName_359AFEED79E48935_PackageProduct.framework')
for framework in *; do
    if [ -d "$framework" ]; then
        if [ -d "${framework}/Frameworks" ]; then
            echo "Removing embedded frameworks from ${framework} to ${PRODUCT_NAME}.app/Frameworks"
            rm -rf "${framework}/Frameworks"
        fi
    fi
done

# codesign for Debugging on device
if [ "${CONFIGURATION}" == "Debug" ] & [ "${SDKROOT}" != *Simulator* ] ; then

    echo "Code signing frameworks..."
    find "${CODESIGNING_FOLDER_PATH}/Frameworks" -maxdepth 1 -name '*.framework' -print0 | while read -d $'\0' framework
    do
        # only sign frameworks without a signature
        if ! codesign -v "${framework}"; then
            codesign --force --sign "${EXPANDED_CODE_SIGN_IDENTITY}" --preserve-metadata=identifier,entitlements --timestamp=none "${framework}"
            echo "Added missing signature to '${framework}'"
        fi
    done
fi
Riband answered 21/9, 2021 at 12:44 Comment(2)
Where do you add (and call) this script?Liggins
This script is part of the Podfile in the main projectRiband
B
0

If non of the above fixes work and you still get errors like "framework xxx not found", verify if the xxx.xcframework and xxx.framework folders inside have the same name.

I spent a few days failing to add .xcframework to Flutter plugin, and it turned out there was a mismatch in naming.

Bechler answered 28/11, 2022 at 15:59 Comment(0)
N
0

I recommend you to pay attention on what Kamil Sikora said, because I had the same problem as him (framework xxx not found).

The name of my .xcframework wasn't the same as the framework itself. Here, I looked into the info.plist file inside the .xcframework and found the real name of the framework. It was the value of the key "LibraryPath". You might find yours there.

Then I renamed the .xcframework file to the same name found in the info.plist file, and it worked.

Also, from Pierre's answer, I only did the first part. I copied the .xcframework file to the plugin/ios folder, modified the podspec, run pod install --repo-update on plugin/example/ios folder and then run the app. I didn't do all that pods stuff.

Nephoscope answered 1/2 at 13:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.