iOS Platform
Build and deploy GreenhouseAdmin as a native iOS application. This guide covers Xcode setup, framework generation, and iOS-specific configuration.
Overview
The iOS platform uses:
Framework : ComposeApp (static framework)
Targets : iosArm64 (devices), iosSimulatorArm64 (M1+ simulators)
HTTP Engine : Darwin (native URLSession)
Minimum iOS : 14.0+
UI Integration : SwiftUI wrapper around Compose Multiplatform
iOS requires macOS and Xcode for development and building.
Prerequisites
Install Xcode
Download and install Xcode from the Mac App Store (Xcode 15.0+). Verify installation:
Install Command Line Tools
Install Xcode command-line tools:
Accept Xcode License
Accept the Xcode license agreement: sudo xcodebuild -license accept
Install CocoaPods (Optional)
For dependency management (if needed): sudo gem install cocoapods
Configuration
iOS Framework Setup
The Kotlin Multiplatform code is compiled into a static iOS framework:
composeApp/build.gradle.kts
listOf (
iosArm64 (), // Physical devices
iosSimulatorArm64 () // M1/M2/M3 Mac simulators
). forEach { iosTarget ->
iosTarget.binaries. framework {
baseName = "ComposeApp"
isStatic = true
}
}
iOS-Specific Dependencies
The iOS platform uses the Darwin engine for Ktor networking:
composeApp/build.gradle.kts
iosMain. dependencies {
// Ktor - Darwin engine for iOS (uses URLSession)
implementation (libs.ktor.client.darwin)
}
Dependency Injection (Koin)
iOS requires platform-specific initialization in the SwiftUI app:
import SwiftUI
import ComposeApp
@main
struct iOSApp : App {
init () {
MainViewControllerKt. doInitKoin ()
}
var body: some Scene {
WindowGroup {
ContentView ()
}
}
}
The doInitKoin() function is exposed from Kotlin:
iosMain/.../MainViewController.kt
fun doInitKoin () {
initKoin ()
}
Development
Running on Simulator
Open iOS Project
Open the iosApp directory in Xcode: open iosApp/iosApp.xcodeproj
Select Simulator
Choose a simulator from the device dropdown (e.g., iPhone 15 Pro).
Build and Run
Click the Run button (⌘R) or use Product > Run.
The first build may take several minutes as Gradle compiles the Kotlin framework.
Generate Framework
Build the iOS framework: ./gradlew :composeApp:linkDebugFrameworkIosSimulatorArm64
Build iOS App
Build the app using xcodebuild: cd iosApp
xcodebuild -project iosApp.xcodeproj \
-scheme iosApp \
-sdk iphonesimulator \
-configuration Debug
Launch Simulator
Start a simulator and install: xcrun simctl boot "iPhone 15 Pro"
xcrun simctl install booted build/Debug-iphonesimulator/iosApp.app
xcrun simctl launch booted com.apptolast.greenhouse.admin
Running on Physical Device
Configure Signing
In Xcode:
Select the iosApp target
Go to Signing & Capabilities
Select your Team
Ensure Automatically manage signing is enabled
Generate Device Framework
Build the framework for physical devices: ./gradlew :composeApp:linkDebugFrameworkIosArm64
Connect Device
Connect your iPhone/iPad via USB and select it in Xcode’s device dropdown.
Build and Run
Click the Run button (⌘R) in Xcode. On first run, you may need to trust the developer certificate on the device:
Settings > General > VPN & Device Management
Viewing Logs
Monitor iOS logs:
Xcode
Console.app
Command Line
Logs appear in the bottom panel: View > Debug Area > Activate Console (⇧⌘C)
Use macOS Console app:
Open Console.app
Select your device or simulator
Filter by process: iosApp
xcrun simctl spawn booted log stream --predicate 'process == "iosApp"'
Building for Distribution
Archive Build
Select Generic iOS Device
In Xcode, select Any iOS Device (arm64) from the device dropdown.
Create Archive
Go to Product > Archive (or ⇧⌘B for build only). This creates an archive in the Xcode Organizer.
Distribute Archive
In the Organizer:
Select the archive
Click Distribute App
Choose distribution method:
App Store Connect : For App Store release
Ad Hoc : For internal testing (up to 100 devices)
Enterprise : For enterprise distribution
Development : For development testing
Command-Line Archive
Build Release Framework
Generate the release framework: ./gradlew :composeApp:linkReleaseFrameworkIosArm64
Create Archive
Build and archive: cd iosApp
xcodebuild archive \
-project iosApp.xcodeproj \
-scheme iosApp \
-sdk iphoneos \
-configuration Release \
-archivePath build/iosApp.xcarchive
Export IPA
Export the IPA file: xcodebuild -exportArchive \
-archivePath build/iosApp.xcarchive \
-exportPath build/iosApp \
-exportOptionsPlist ExportOptions.plist
Create ExportOptions.plist with your distribution method: <? xml version = "1.0" encoding = "UTF-8" ?>
<! DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd" >
< plist version = "1.0" >
< dict >
< key > method </ key >
< string > app-store </ string >
< key > teamID </ key >
< string > YOUR_TEAM_ID </ string >
</ dict >
</ plist >
SwiftUI Integration
The iOS app wraps the Compose Multiplatform UI in SwiftUI:
import SwiftUI
import ComposeApp
struct ContentView : View {
var body: some View {
ComposeView ()
. ignoresSafeArea (. all )
}
}
struct ComposeView : UIViewControllerRepresentable {
func makeUIViewController ( context : Context) -> UIViewController {
return MainViewControllerKt. createMainViewController ()
}
func updateUIViewController ( _ uiViewController : UIViewController, context : Context) {}
}
Expose the Compose UI from Kotlin:
iosMain/.../MainViewController.kt
fun createMainViewController (): UIViewController {
return ComposeUIViewController {
App () // Shared Compose UI from commonMain
}
}
iOS-Specific Features
Safe Area Handling
Handle iOS safe areas in Compose:
iosMain/.../SafeAreaInsets.kt
@Composable
actual fun getSystemInsets (): WindowInsets {
val safeArea = LocalSafeArea.current
return WindowInsets (
left = safeArea.left.dp,
top = safeArea.top.dp,
right = safeArea.right.dp,
bottom = safeArea.bottom.dp
)
}
Native iOS APIs
Access iOS-specific APIs using expect/actual:
iosMain/.../PlatformUtils.kt
import platform.UIKit.UIDevice
import platform.Foundation.NSUUID
actual fun getDeviceId (): String {
return UIDevice.currentDevice.identifierForVendor?.UUIDString ?: ""
}
actual fun shareText (text: String ) {
val activityViewController = UIActivityViewController (
activityItems = listOf (text),
applicationActivities = null
)
// Present activity view controller
}
Info.plist Configuration
Configure app permissions and settings:
< key > NSAppTransportSecurity </ key >
< dict >
< key > NSAllowsArbitraryLoads </ key >
< false />
< key > NSExceptionDomains </ key >
< dict >
< key > api.example.com </ key >
< dict >
< key > NSExceptionAllowsInsecureHTTPLoads </ key >
< false />
< key > NSIncludesSubdomains </ key >
< true />
</ dict >
</ dict >
</ dict >
< key > UIRequiredDeviceCapabilities </ key >
< array >
< string > arm64 </ string >
</ array >
Testing
TestFlight Distribution
Archive for App Store
Create an App Store archive as described above.
Upload to App Store Connect
In Xcode Organizer:
Select the archive
Click Distribute App
Choose App Store Connect
Follow the prompts to upload
Configure TestFlight
In App Store Connect:
Go to TestFlight tab
Add internal/external testers
Submit for beta review (external testers only)
Simulator Testing
Test on multiple simulators:
# List available simulators
xcrun simctl list devices
# Create a new simulator
xcrun simctl create "iPhone 15 Pro" "iPhone 15 Pro" "iOS17.0"
# Boot simulator
xcrun simctl boot "iPhone 15 Pro"
# Install and launch
xcrun simctl install booted path/to/app
xcrun simctl launch booted com.apptolast.greenhouse.admin
Troubleshooting
Error : ld: framework not found ComposeAppSolution : Build the iOS framework first:./gradlew :composeApp:linkDebugFrameworkIosSimulatorArm64
Or in Xcode, add a Run Script phase: cd " $SRCROOT /.."
./gradlew :composeApp:embedAndSignAppleFrameworkForXcode
Signing certificate errors
Error : No signing certificate foundSolution :
Ensure you’re logged into Xcode with your Apple ID
Go to Xcode > Settings > Accounts
Select your team and click Manage Certificates
Create a new certificate if needed
Darwin engine network errors
Error : SSL error with Darwin clientSolution : Check App Transport Security settings in Info.plist. For development, you may need to allow insecure loads for specific domains.
Error : Need to run on Intel MacSolution : Add iosX64() target to build.gradle.kts:listOf (
iosArm64 (),
iosSimulatorArm64 (),
iosX64 () // For Intel Mac simulators
). forEach { .. . }
Koin initialization crashes
Error : App crashes on launch with Koin errorsSolution : Ensure doInitKoin() is called in the SwiftUI app’s init():@main
struct iOSApp : App {
init () {
MainViewControllerKt. doInitKoin ()
}
...
}
Next Steps
Android Platform Build and deploy to Android devices
Web Platform Deploy as a web application
Multiplatform Share code between platforms
Architecture MVVM architecture and patterns