Custom Universal Framework in iOS
By: Priya Talreja
Tags: Custom, Swift, Framework, iOS, Universal

Universal framework is required so that we can run the framework on both simulator and real device.

Creating Universal Aggregate

Open your framework project in Xcode, File → New → Target → Cross Platform→ Aggregate.

2. Click “Next”

3. Give Suitable Name to the Aggregate Scheme such as

Universal-<FrameworkName>

4. Click on Project → Universal-<FrameworkName> Target → Build Phases → Click “+” → New Run Script Phase.

# Set bash script to exit immediately if any commands fail. set -e # Setup some constants for use later on. FRAMEWORK_NAME = <FRAMEWORK_NAME> OUTPUT_DIR= "${SRCROOT}/build"

# If remains from a previous build exist,delete them. if [ -d "${OUTPUT_DIR}" ]; then rm -rf "${OUTPUT_DIR}" fi

# Build the framework for device and for simulator. echo "Building for device" xcodebuild -project "${FRAMEWORK_NAME}.xcodeproj" -scheme "${FRAMEWORK_NAME}" -configuration Release -arch arm64 -arch armv7 -arch armv7s only_active_arch=no defines_module=yes -sdk "iphoneos" -derivedDataPath "${OUTPUT_DIR}" echo "Building for Simulator" xcodebuild -project "${FRAMEWORK_NAME}.xcodeproj" -scheme "${FRAMEWORK_NAME}" -configuration Release -arch x86_64 -arch i386 only_active_arch=no defines_module=yes -sdk "iphonesimulator" -derivedDataPath "${OUTPUT_DIR}"

# Remove .framework file if exists from previous run. if [ -d "${OUTPUT_DIR}/${FRAMEWORK_NAME}.framework" ]; then rm -rf "${OUTPUT_DIR}/${FRAMEWORK_NAME}.framework" fi # Copy the device version of framework. cp -r "${OUTPUT_DIR}/Build/Products/Release-iphoneos/${FRAMEWORK_NAME}.framework" "${OUTPUT_DIR}/${FRAMEWORK_NAME}.framework"

# Merging the device and simulator frameworks' executables with

# lipo. lipo -create -output "${OUTPUT_DIR}/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}" "${OUTPUT_DIR}/Build/Products/Release-iphoneos/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}" "${OUTPUT_DIR}/Build/Products/Release-iphonesimulator/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}" # Copy Swift module mappings for simulator into the framework. cp -r "${OUTPUT_DIR}/Build/Products/Release-iphonesimulator/${FRAMEWORK_NAME}.framework/Modules/${FRAMEWORK_NAME}.swiftmodule/" "${OUTPUT_DIR}/${FRAMEWORK_NAME}.framework/Modules/${FRAMEWORK_NAME}.swiftmodule" cp -r "${OUTPUT_DIR}/Build/Products/Release-iphoneos/${FRAMEWORK_NAME}.framework/Modules/${FRAMEWORK_NAME}.swiftmodule/" "${OUTPUT_DIR}/${FRAMEWORK_NAME}.framework/Modules/${FRAMEWORK_NAME}.swiftmodule"

# Create new combined simulator and device swift header file. COMBINED_PATH="${BUILD_DIR}/iOS + iOS Simulator/${PROJECT_NAME}-Swift.h"

mkdir -p "${BUILD_DIR}/iOS + iOS Simulator/"

touch "${COMBINED_PATH}" echo "#ifndef TARGET_OS_SIMULATOR

#include <TargetConditionals.h>

#endif

#if TARGET_OS_SIMULATOR" >> "${COMBINED_PATH}"

cat "${OUTPUT_DIR}/Build/Products/Release-iphonesimulator/${FRAMEWORK_NAME}.framework/Headers/${FRAMEWORK_NAME}-Swift.h" >> "${COMBINED_PATH}" echo "#else" >> "${COMBINED_PATH}"

echo "//Start of iphoneos" >> "${COMBINED_PATH}" cat "${OUTPUT_DIR}/Build/Products/Release-iphoneos/${FRAMEWORK_NAME}.framework/Headers/${FRAMEWORK_NAME}-Swift.h" >> "${COMBINED_PATH}" echo "#endif" >> "${COMBINED_PATH}" # Overwrite generated -Swift.h file with combined -Swift.h file cat "$COMBINED_PATH" > "${OUTPUT_DIR}/${FRAMEWORK_NAME}.framework/Headers/${FRAMEWORK_NAME}-Swift.h" #Optional Step to copy the framework to root folder cp -r "${OUTPUT_DIR}/${FRAMEWORK_NAME}.framework" "${SRCROOT}"

5. Select Universal<FrameworkName>Scheme and Generic iOS Device as Target.

6. Product → Build

This will create a Universal Framework in the Output Folder.

Remove Unused Architecture

Apple doesn’t allow the application with unused architectures to the App Store so we need to remove unused architectures.

We need to add run script in the application that uses our custom universal framework.

APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}" # This script loops through the frameworks embedded in the application and # removes unused architectures. find "$APP_PATH" -name '*.framework' -type d | whileread -r FRAMEWORK do FRAMEWORK_EXECUTABLE_NAME=$(defaults read"$FRAMEWORK/Info.plist" CFBundleExecutable) FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME" echo "Executable is $FRAMEWORK_EXECUTABLE_PATH" EXTRACTED_ARCHS=()

GOOD_ARCHS=() PRESENT_ARCHS=($(lipo -archs "$FRAMEWORK_EXECUTABLE_PATH")) if [[ "${#PRESENT_ARCHS[@]}" -lt 2 ]]

then

echo "Framework is not a Fat binary, skipping..."

continue

fi for ARCH in "${PRESENT_ARCHS[@]}"

do

if [[ "$ARCH" != x86_64 && "$ARCH" != i386 ]]

then

echo "Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME" lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH" GOOD_ARCHS+=("$ARCH")

EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")

fi

done echo "Merging extracted architectures: ${ARCHS}" lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}" rm "${EXTRACTED_ARCHS[@]}"

echo "Replacing original executable with thinned version" rm "$FRAMEWORK_EXECUTABLE_PATH" mv "$FRAMEWORK_EXECUTABLE_PATH-merged""$FRAMEWORK_EXECUTABLE_PATH" done

This run script removes the unused Simulator architectures only while pushing the Application to the App Store.

Above script solves ERROR ITMS-90085: “No architectures in the binary. Lipo failed to detect any architectures in the bundle executable.”

Now Custom Universal iOS Framework is Complete!

Thank you. Hope this helps you. Please let me know if you have any queries.