Начало работы, шаг за шагом
18 апреля 2023 г.: В преддверии KotlinConf 2023 многоплатформенная поддержка Compose для iOS была повышена с экспериментальной до альфа-версии. Официальная документация значительно улучшена, но все, что я вам здесь показываю, остается в силе.
27 февраля 2023 г.: Спасибо Владимир Стельмащук за pull request.
Обновлено 13 февраля 2023 г.
Код:
Я рекомендую использовать последнюю версию Android Studio.
Jetpack Compose, пожалуй, самое большое и лучшее, что случилось с разработкой Android с самого начала Android; это прямо там с принятием Kotlin — инженерный мрамор и абсолютная радость в использовании.
Благодаря усилиям Jetbrains по переносу Compose на мультиплатформу мы видим интересные способы создания приложений для Android и настольных компьютеров из единой базы кода. Но как насчет iOS? Оказывается, это тоже возможно… хотя не все так гладко. Сейчас я покажу вам, как это сделать, шаг за шагом.
Предпосылки
- Это очевидно, но стоит отметить. Вы можете сделать это только из MacOS. Да, это Compose, но вам все еще нужны такие вещи, как симулятор.
- Возможно, вы не сможете использовать все библиотеки, к которым вы привыкли из своей разработки для Android: компоненты архитектуры, библиотеки аккомпаниатора и т. д., потому что они не являются мультиплатформенными (пока?), но по большей части Compose будет работать так, как вы ожидаете. потому что поддержка iOS пока экспериментальная.
Первый подход
Вам нужен модуль для вашего приложения Compose для iOS. Это может быть отдельное приложение с приложением для iOS, являющимся единственным модулем в проекте (так же, как вы делаете это для нативной разработки под Android), или вы можете добавить еще один модуль в свой проект KMM.
Если вы хотите создать для этого новый проект, выберите Compose Multiplatform в IntelliJ IDEA или в новом мастере проектов AndroidStudio.
Внутри только что созданного модуля/проекта скопируйте и вставьте это суть в build.gradle.kts
и используйте это суть для settings.gradle.kts
.
Несколько вещей, чтобы объяснить о build.gradle.kts
.
id("ord.jetbrains.compose") version "1.3.0-rc01
, строка 7, и maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
, строка 16 — это способ добавления Compose Multiplatform в приложение.
Мы определяем цели следующим образом:
iosX64("uikitX64") { binaries { executable { entryPoint = "main" freeCompilerArgs += listOf( "-linker-option", "-framework", "-linker-option", "Metal", "-linker-option", "-framework", "-linker-option", "CoreText", "-linker-option", "-framework", "-linker-option", "CoreGraphics" ) } } } iosArm64("uikitArm64") { binaries { executable { entryPoint = "main" freeCompilerArgs += listOf( "-linker-option", "-framework", "-linker-option", "Metal", "-linker-option", "-framework", "-linker-option", "CoreText", "-linker-option", "-framework", "-linker-option", "CoreGraphics" ) freeCompilerArgs += "-Xdisable-phases=VerifyBitcode" } } }
Ничего особенного, верно?
Теперь давайте посмотрим на блок compose.experimental
, который требует дополнительных пояснений. uikit.application
позволяет настроить приложение для iOS. Вы видите такие вещи, как bundleIdPrefix
и projectName
. bundleIdPrefix
похоже на applicationId
для нас, разработчиков Android, а projectName
говорит само за себя.
Теперь давайте перейдем к deployConfiguration
. Вы развертываете/запускаете свое iOS-приложение с задачами Gradle; вы можете увидеть их в комментариях. Это iosDeployIPhone13ProDebug
и iosDeployIPadDebug
. Мы также определяем устройство, на котором мы хотим, чтобы эта задача запускала наше приложение. Имя, которое вы укажете внутри simulator("xxx")
, будет определять вашу задачу Gradle для этой конфигурации, и оно будет называться xxx
. Задача Gradle будет iosDeployxxxDebug
.
Возможно, вам уже интересно, что такое teamId
. Честно говоря, я не совсем уверен, но думаю, что это нужно для сборки релизной версии вашего приложения; отладка будет работать нормально без него. Для этого вам необходимо членство Apple Developer. Вы получите teamId
оттуда.
Вот и все для настройки Gradle. Перейдем к файловой структуре. Внутри module/src
вам нужно создать uikitMain/kotlin
, а внутри него вам нужен файл main.uikit.kt
. Это должно выглядеть так:
iosCompose
— имя, которое я выбираю. Вы можете выбрать что-то другое.
Внутри main.uikit.kt
вставьте следующий код:
Это минимальная настройка, и после того, как вы на нее посмотрите, она перестает пугать. На данный момент вам даже не нужно ничего об этом знать, за исключением того, что ваше приложение написано в строке 43 этого описания:
window!!.rootViewController = Application("Your Application") { Text("Helo World, from Compose iOS app!!!") }
Application
— это функция, которая принимает функцию @Composable
. Это ваша точка входа в мир Compose, и теперь вы можете использовать свои навыки Compose, как обычно.
Осталось только запустить приложение. Но не так быстро. Нам нужен еще один шаг. Вам нужно будет сделать это только в том случае, если вы никогда не запускали какое-либо приложение iOS на симуляторе из Xcode.
И это ваша задача на сегодня.
Создайте новое приложение iOS в Xcode и запустите приложение. Это необходимо, потому что вам нужен сертификат. Xcode предложит вам установить его, для чего потребуется пароль login.keychain
.
И, наконец, откройте свой терминал и запустите ./gradlew iosDeployIPhone13ProDebug
или найдите эту задачу в окне инструмента Gradle в Android Studio или IntellijIDEA.
Я должен отметить, что этот подход очень ограничен. Все, что вы хотите использовать, должно быть в одном модуле. Мне не удалось импортировать @Composable
s из модуля shared
, и вы не можете обмениваться @Composable
s между платформами. наш модуль полностью автономен, и у нас нет проекта для приложения iOS.
Существует лучший способ…
Второй подход
Подход, который мы рассмотрим сейчас, намного лучше: мы сможем повторно использовать @Composable
между платформами, и у нас будет доступ к нативному приложению iOS.
Для этого подхода у нас будут все наши @Composable
в нашем модуле shared
.
Для этого нам понадобится классическая мультиплатформенная установка Compose:
settings.gradle.kts
pluginManagement { repositories { google() gradlePluginPortal() maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") mavenCentral() } }
2. Уровень проекта build.gradle.kts
plugins { //trick: for the same plugin versions in all sub-modules id("com.android.application").version("8.1.0-alpha04").apply(false) id("com.android.library").version("8.1.0-alpha04").apply(false) id("org.jetbrains.compose").version("1.3.0").apply(false) kotlin("android").version("1.8.0").apply(false) kotlin("multiplatform").version("1.8.0").apply(false) id("org.jetbrains.kotlin.jvm") version "1.8.0" apply false } allprojects { repositories { google() mavenCentral() maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") } }
3. shared
модуль build.gradle.kts
plugins { kotlin("multiplatform") kotlin("native.cocoapods") id("com.android.library") id("org.jetbrains.compose") } kotlin { android() ios() iosSimulatorArm64() cocoapods { summary = "Some description for the Shared Module" homepage = "Link to the Shared Module homepage" version = "1.0" ios.deploymentTarget = "14.1" podfile = project.file("../ios/Podfile") framework { baseName = "shared" isStatic = true } } sourceSets { val commonMain by getting { dependencies { implementation(compose.ui) implementation(compose.foundation) implementation(compose.material) implementation(compose.runtime) } } val androidMain by getting val iosMain by getting { dependsOn(commonMain) } val iosSimulatorArm64Main by getting { dependsOn(iosMain) } } } android { namespace = "com.example.compose_ios" compileSdk = 33 defaultConfig { minSdk = 24 targetSdk = 33 } compileOptions { sourceCompatibility = JavaVersion.VERSIONbuild.gradle.kts
8 targetCompatibility = JavaVersion.VERSIONbuild.gradle.kts
8 } }
И с этим установка готова. Перейдем к реализации.
Главное здесь то, что наши @Composable
s внутри shared
модуля должны быть внутренними.
внутренний означает, что он не будет виден за пределами модуля, в котором он определен, в нашем случае shared
, так как же мы собираемся делиться @Composable
s? У нас будут файлы main.android.kt
и main.ios.kt
внутри shared/androidMain
и shared/iosMain
соответственно. Оттуда мы будем вызывать наши внутренние @Composable
s. Так:
shared/commonMain
. Это наше фактическое приложение, которым мы будем делиться на разных платформах.
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @Composable internal fun ExampleApplication() { MaterialTheme { Scaffold( topBar = { TopAppBar { Text("Hello") } }, content = { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center, ) { Text("From Compose!") } }, ) } }
2. shared/androidMain
, main.android.kt
. Это будет видно из нашего приложения/проекта для Android.
import androidx.compose.runtime.Composable @Composable fun Application() { ExampleApplication() }
3. shared/iosMain
, main.ios.kt
. Здесь определяются ViewController
, которые будут видны из нашего приложения/проекта iOS.
import androidx.compose.ui.window.Application import platform.UIKit.UIViewController fun MainViewController(): UIViewController = Application("Example Application") { ExampleApplication() }
Как и в первом подходе, Application
— это наш вход в мир Compose.
Настройка этого ViewController
для приложения iOS:
//iosApp.swift import SwiftUI import shared @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) let mainViewController = Main_iosKt.MainViewController() window?.rootViewController = mainViewController window?.makeKeyAndVisible() return true } }
4. Нажмите эту кнопку запуска. В этом случае мы не используем эту специальную задачу Gradle. Просто запустите созданную для нас конфигурацию ios при создании проекта.