21. 12. 2023
Na Internetu lze najít spoustu návodů na sdílený kód mezi systémy pomocí Dynamických Frameworků, ale některé už nefungují, nebo jsou zbytečně složité. Tak jsem se rozhodl sepsat tutoriál a natočit video, které bude co nejkratší a shrne to vše do jednoho.
Hledáš jenom základní iOS Kuchařku? Podívejte se sem: 18+ věcí, které by měl dobrý iOS vývojář zvládat.
Jak už bylo rečeno, když chceme sdílet kód mezi iOS, tvOS, watchOS nebo macOS, tak nejjednodušší způsob je využít Dynamický Framework, který je na iOS dostupný od iOS 8.
Tutoriál lze rozdělit na tři části:
- Vytvoření frameworku obsahujícího náš sdílený kód.
- Nakonfigurování frameworku pro zařízení a simulátory.
- Použítí frameworku v iOS a macOS.
Pro ty, co jsou líní číst, jsem připravil video tutoriál:
Vytvoření Dynamického Frameworku
Otevřeme Xcode a vytvoříme nový projekt. File -> New -> Project. Zvolíme iOS -> Framework & Library -> Cocoa Touch Framework a pojmenujeme ho MyKit.
Po vytvoření projektu přejmenujeme vytvořený target v projektu z MyKit na iOS MyKit a taktéž přejmenujeme vytvořené schéma pro daný target. Schemes -> Manage Schemes
Přidáme nový target pro macOS platformu a pojmenujeme ho macOS MyKit.
Odstraníme složku s názvem macOS MyKit, která se vytvořila s novým targetem pro macOS.
Projekt bude vypadat nějak takhle:
Pro každý target otevřeme Build Settings a vyhledáme Product Name a změníme hodnotu na MyKit. Tím docílíme toho, že pro každou platformu se náš framework bude jmenovat stejně.
Následně pro každý target znova v Build Settings vyhledáme Info.plist File a hodnotu z targetu iOS MyKit překopírujeme do ostatních targetů. Tím zajístíme jeden Info.plist soubor pro všechny targety.
Vytvoříme náš první zdrojový soubor File -> New -> File a vybereme iOS -> Source -> Swift File a pojmenujeme ho Model. Nezapomeneme zaškrtnout všechny targety.
Obsah souboru Model.swift:
import Foundation public class Model { public let devices: [String] public init() { self.devices = ["iPhone", "iPad", "iPod"] } }
Vytvoříme další soubor, tentokrát shell script. File -> New -> File a vybereme iOS -> Other -> Shell Script. Pojmenujeme ho UniversalFramework a vybereme target pouze iOS MyKit.
Obsah souboru UniversalFramework.sh:
if [ "true" == ${ALREADYINVOKED:-false} ] then echo "RECURSION: Detected, stopping" else export ALREADYINVOKED="true" UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-iosuniversal # make sure the output directory exists mkdir -p "${UNIVERSAL_OUTPUTFOLDER}" # Step 1. Build Device and Simulator versions xcodebuild -target "${TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build xcodebuild -target "${TARGET_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build # Step 2. Copy the framework structure (from iphoneos build) to the universal folder cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/" # Step 3. Copy Swift modules from iphonesimulator build (if it exists) to the copied framework directory SIMULATOR_SWIFT_MODULES_DIR="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule/." if [ -d "${SIMULATOR_SWIFT_MODULES_DIR}" ]; then cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule" fi # Step 4. Create universal binary file using lipo and place the combined executable in the copied framework directory lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}" fi
Modifikoval jsem TENTO originální skript.
Nově vytvořený shell soubor je potřeba nastavit jako spustitelný. Otevřeme Terminal. Applications -> Utitlities -> Terminal. Otevřeme si soubor UniversalFramework.sh ve Finderu.
Napíšeme příkaz:
$ chmod u+x "path_to_file"
Přejdeme zpět do Xcode a vybereme Projekt MyKit a target iOS MyKit a následně se přepneme do Build Phases.
Klikneme na tlačítko + a vybereme New Run Script Phase a pojmenujeme si to jako Universal Framework.
Vložíme shell příkaz:
${SRCROOT}/UniversalFramework.sh
Vybereme iOS MyKit schéma a libovolný simulátor zařízení.
Spustíme build vybraného schématu. Product -> Build
Přepneme schéma na macOS MyKit a spustíme build.
V levém sloupci vybereme složku Products a klikneme pravým tlačítkem na libovolný MyKit.framework a zvolíme Show in Finder.
Po otevření vidíme složky Debug-XYZ. Pro nás je duležitá složka Debug, která obsahuje MyKit framework pro macOS a složka Debug-iosuniversal, která obsahuje MyKit framework pro iOS a Simulator.
Tím je náš framework hotový a připraven k použití 🙂
Pojdme se teď podívát na to, jak ho použít v iOS a macOS aplikaci. Na iOS aplikaci bude potřeba dodělat odstranění x86/64 architektury z frameworku při nahrávání do iTunes.
Použití frameworku
Vytvoříme nový Workspace. File -> New -> Workspace. A pojmenujeme ho MyApp. Doporučuji si vytvořit stejnojmennou složku a do té vložit workspace soubor.
V levém dolním rohu stiskneme tlačítko + a zvolíme New Project.
Vybereme iOS -> Application -> Single View Application a pojmenujeme to jako MyApp. Doporučuji vytvořit složku iOS a do ní umístit projekt.
Znovu vytvoříme nový projekt ale tentokrát macOS aplikaci. OS X -> Application -> Cocoa Application a pojmenujeme stéjně MyApp. Zase doporučuji vytvořit složku macOS a do ní vložit projekt.
Ve workspace vybereme iOS aplikaci a klikneme na záložku General. Ze složky Debug-iosuniversal, kterou jsme otevřeli na konci vytváření frameworku, přetáhneme MyKit.framework do Embedded Binaries. Ujistíme se, že check box Copy Items If needed je odškrtnutý.
Stejnou akci provedeme pro macOS aplikaci. Zde akorát přetáhneme MyKit.framework ze složky Debugvb.
Jak pro iOS, tak pro macOS aplikaci v záložce Build Settings vyhledáme Framework Search Paths a přidáme cestu k frameworku. Pro iOS to bude cesta ke složce Debug-iosuniversal a pro macOS složka Debug.
Nastal čas použít náš framework v aplikacích. Vybereme soubor ViewController.swift v iOS aplikaci a nahradíme ho tímhle:
import UIKit import MyKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let model = Model() print(model.devices) // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
Stejnou akci provedeme pro macOS aplikaci. Takže soubor ViewController.swift v macOS aplikaci nahradíme timhle:
import Cocoa import MyKit class ViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() let model = Model() print(model.devices) // Do any additional setup after loading the view. } override var representedObject: AnyObject? { didSet { // Update the view, if already loaded. } } }
Pro iOS aplikaci vytvoříme nový shell script. File -> New -> File a potom iOS -> Other -> Shell Script a pojmenujeme ho Trim.sh
Obsah souboru Trim.sh:
FRAMEWORK=$1 echo "Trimming $FRAMEWORK..." FRAMEWORK_EXECUTABLE_PATH="${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/$FRAMEWORK.framework/$FRAMEWORK" EXTRACTED_ARCHS=() for ARCH in $ARCHS do echo "Extracting $ARCH..." lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH" EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH") done echo "Merging binaries..." lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}" rm "${EXTRACTED_ARCHS[@]}" rm "$FRAMEWORK_EXECUTABLE_PATH" mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH" echo "Done."
Zdroj ZDE
Opět přes terminál nastavíme soubor jako spustitelný.
$chmod u+x "cesta_k_souboru"
V záložce Build Phases klikneme na tlačítko + a vybereme New Run Script Phase a pojmenujeme si to jako Trim Framework.
Vložíme shell příkaz:
${SRCROOT}/Trim.sh MyKit
Vybereme schéma pro iOS aplikaci a spustíme bulid. Product -> Build.
Vybereme schéma pro macOS aplikaci a spustíme build.
A máme hotovo!