How to resolve Apple’s Invalid Swift Support Issue

Say you’re part of an iOS mobile development team with its own Apple Developer Enterprise account. You create an app, you distribute it to other employees at your company, and everything works great.

But now say you need to send your app to another team who will distribute it via the App Store. So you send them your Enterprise .ipa, they resign it with their standard Apple Developer (App Store) account, and they submit it to Apple. If your app is sufficiently complex, you may receive this error:

ITMS_90426: Invalid Swift Support – The SwiftSupport folder is missing. Rebuild your app using the current public (GM) version of Xcode and resubmit it.

Why does this happen? Why does it matter which type of account was used to generate the .ipa?

Well, App Store .ipas are very different from Enterprise ones:

As you can see above, Enterprise .ipas don’t include the SwiftSupport folder. This has to do with the processing Apple has to do before it will release an app to the App Store. So what are your options? How can you resolve this Invalid Swift Support issue?

Get an App Store account

The easiest thing to do would be to get your own standard Apple Developer (App Store) account. When you generate the .ipa using that account it will include all of the necessary files and Apple won’t reject it once it’s submitted. But that’s no fun, and it’ll cost you $99/year.

Manually add the SwiftSupport folder

It turns out, the SwiftSupport folder is included in the .xcarchive that is used to generate an .ipa. It can also be generated by scanning the .app Frameworks directory for .dylib files, and copying the matching ones from the Xcode toolchain.

Even knowing all of this, it’s still not easy to actually execute. The folders need to have a certain structure with certain names, the .ipa needs to be unzipped and zipped back up, etc. That’s why I created a script that will do it for you:

#!/bin/bash

for ARGUMENT in "$@"
do

    KEY=$(echo $ARGUMENT | cut -f1 -d=)
    VALUE=$(echo $ARGUMENT | cut -f2 -d=)

    case "$KEY" in
            ipa_path)          ipaPath=${VALUE} ;; # Format: "Path/to/app.ipa"
            archive_path)      archivePath=${VALUE} ;; # Format: "Path/to/app.xcarchive"
            toolchain_path)    toolchainPath=${VALUE} ;; # Format: "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/iphoneos"
            *)
    esac

done

# Derived Variables
ipaDirectory=$(dirname "$ipaPath")
ipaName=$(basename "$ipaPath")
zipName=${ipaName/.ipa/.zip}
appName=${ipaName/.ipa/}
zipSuffix=-unzipped
unzippedDirectoryName=${appName}${zipSuffix}
newIpaSuffix=-with-swift-support
newIpaName=${appName}${newIpaSuffix}
swiftSupportPath=SwiftSupport/iphoneos
ipaSwiftSupportDirectory=${ipaDirectory}/${unzippedDirectoryName}/${swiftSupportPath}

# Changes the .ipa file extension to .zip and unzips it
function unzipIPA {
    mv "${ipaDirectory}/${ipaName}" "${ipaDirectory}/${zipName}"
    unzip "${ipaDirectory}/${zipName}" -d "${ipaDirectory}/${unzippedDirectoryName}"
}

# Copies the SwiftSupport folder from the .xcarchive into the .ipa
function copySwiftSupportFromArchiveIntoIPA {
    mkdir -p "$ipaSwiftSupportDirectory"
    cd "${archivePath}/${swiftSupportPath}"
    for file in *.dylib; do
        cp "$file" "$ipaSwiftSupportDirectory"
    done
}

# Creates the SwiftSupport folder from the Xcode toolchain and copies it into the .ipa
function copySwiftSupportFromToolchainIntoIPA {
    mkdir -p "$ipaSwiftSupportDirectory"
    cd "${ipaDirectory}/${unzippedDirectoryName}/Payload/${appName}.app/Frameworks"
    for file in *.dylib; do
      cp "${toolchainPath}/${file}" "$ipaSwiftSupportDirectory"
    done
}

# Adds the SwiftSupport folder from one of two sources depending on the presence of an .xcarchive
function addSwiftSupportFolder {
  if [ -z "$archivePath" ]
  then
    copySwiftSupportFromToolchainIntoIPA
  else
    copySwiftSupportFromArchiveIntoIPA
  fi
}

# Zips the new folder back up and changes the extension to .ipa
function createAppStoreIPA {
    cd "${ipaDirectory}/${unzippedDirectoryName}"
    zip -r "${ipaDirectory}/${newIpaName}.zip" ./*
    mv "${ipaDirectory}/${newIpaName}.zip" "${ipaDirectory}/${newIpaName}.ipa"
}

# Renames original .ipa and deletes the unzipped folder
function cleanUp {
    mv "${ipaDirectory}/${zipName}" "${ipaDirectory}/${ipaName}"
    rm -r "${ipaDirectory}/${unzippedDirectoryName}"
}

# Execute Steps
unzipIPA
addSwiftSupportFolder
createAppStoreIPA
cleanUp

Usage

First, save a copy of the script and make it executable like so:

chmod +x path/to/script

Then, if you have access to the .xcarchive, you can execute the script like this:

path/to/script \
    ipa_path="path/to/app.ipa" \
    archive_path="path/to/app.xcarchive"

If you don’t have access to the .xcarchive, the SwiftSupport folder can still be generated from the Xcode toolchain. In this case, provide the path to the SwiftSupport .dylibs for your version of Xcode and target sdk, making sure that the toolchain_path you provide contains a list of .dylib files:

path/to/script \
    ipa_path="path/to/app.ipa" \
    toolchain_path="/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/iphoneos"

Either of these approaches will create a new .ipa in the same directory as the original one, but with -with-swift-support added to its name. And if you submit a resigned version of this .ipa, Apple will have its precious SwiftSupport folder and you can get on with your life.

I hope this can help someone and thanks for reading!