moko-kswift
moko-kswift
moko-kswift - этот плагин, позволяет автоматически генерировать Swift-friendly API из общего кода:
enum
-ы, соответствующиеsealed-interface
-ам из общего кода, чтобы использовать их вswitch
без веткиdefault
extensions
к платформенным классам (UILabel
и тд) и интерфейсам
Детали подключения плагина вы можете узнать из его README и статьи.
Способы подключения
Cocapods
При подключении плагина moko-kswift
он добавит следующие таски в gradle. Имя таски генерируется по принципу: kSwift
+ framework name
+ Podspec
, находиться они будут в группе cocoapods
.
:mpp-library:kSwiftMultiplatformLibraryPodspec
- если подключен наш плагин dev.icerock.mobile.multiplatform.ios-framework:mpp-library-pods:kSwiftmpp_library_podsPodspec
- если подключен плагин cocoapods от JetBrains
Обе эти таски генерируют .podspec
файл. Его имя будет: framework_name
+ Swift.podspec
, например mpp-library/MultiplatformLibrarySwift.podspec
.
После этого нужно просто подключить его pod MultiplatformLibrarySwift
в iosApp/Podfile
и использовать import MultiplatformLibrarySwift
в нужном файле.
Этот вариант более предпочтителен к использованию, потому что
- Меньше конфликтов с именами, сгенерированные классы и методы доступны только там, где мы его подключили
- Лучше воспроизводимость -
buildPhase
на сборку Kotlin-кода всегда происходит при компиляции пода. Реже будет происходить непонятная ошибка из-за не скомпилированного заранее Kotlin модуля.
Однако, сгенерированные файлы можно подключить и вручную, генерируются они по пути ../framework_name/build/cocoapods/framework/framework_nameSwift/..
Напрямую
Если фреймворк общего кода подключен к iOS-проекту напрямую, то сгенерированные файлы подключить при помощи cocoapods
не получится, потому что pod MultiplatformLibrarySwift
внутри себя имеет зависимость от основного фреймворка - MultiplatformLibrary
.
Чтобы сгенерированные файлы всегда находились в одном месте, можно добавить таску в shared_module/build.gradle
, которая переместит сгенерированные файлы в build/generated/swift
. После этого нужно просто подключить их вручную к iOS проекту.
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink>().matching {
it.binary is org.jetbrains.kotlin.gradle.plugin.mpp.Framework
}.configureEach {
doLast {
val swiftDirectory = File(destinationDir, "${binary.baseName}Swift")
val xcodeSwiftDirectory = File(buildDir, "generated/swift")
swiftDirectory.copyRecursively(xcodeSwiftDirectory, overwrite = true)
}
}
При самой первой сборке iOS проекта, без предварительной сборки Kotlin, Xcode будет ругаться, что у него нет сгенерированных файлов. Поэтому нужно вручную предварительно компилировать Kotlin Framework и только потом собирать iOS в Xcode.
mvvm-livedata, mvvm-flow и moko-kswift в одном проекте
Проблема
Начиная с moko-mvvm-0.13.0 появилась поддержка декларативного UI - Jetpack Compose
и SwiftUI
. Она основана на mvvm-flow
, без mvvm-livedata
.
Однако, на момент написания, часть библиотек еще использовала модуль mvvm-livedata
moko-paging:0.7.1
moko-fields:0.9.0
- ...
Поэтому, пока все библиотеки не обновятся до поддержки и mvvm-flow
и mvvm-livedata
, нам иногда придется подключать оба этих модуля. В этом случае возникает проблема с генерацией extensions
для iOS
.
В mvvm-livedata
и в mvvm-flow
есть экстеншены с одинаковыми именами, для биндинга UI элемента к State
.
Компилятор Kotlin/Native
видит конфликты имен, чтобы их избежать он создаст эти экстеншены с _
.
Однако, плагин moko-kswift
ничего не знает про новые измененные названия extensions
c _
, он ожидает экстеншены с такими же именами, какие были в Kotlin
, поэтому сгенерированный код окажется некорректным (будет использовать не верные имена функций).
Решение
В mpp-library/src/iosMain/...
создаем файл со всеми экстеншенами из mvvm-flow
или mvvm-livedata
, которые понадобятся нам на платформах, например:
import dev.icerock.moko.mvvm.livedata.bindTextTwoWay
import dev.icerock.moko.mvvm.flow.bindTextTwoWay
// ...
fun UITextField.bindTextTwoWay(livedata: MutableLiveData<String>) = bindTextTwoWay(livedata)
fun UITextField.bindTextTwoWay(flow: CStateFlow<String>) = bindTextTwoWay(flow)
Оригинальные модули библиотек нужно будет добавить в исключения для moko-kswift
, чтобы генерация происходила только на основе файла из iosMain
.
kswift {
excludeLibrary("mvvm-livedata")
excludeLibrary("mvvm-flow")
}
На основе этого moko-kswift
успешно сгенерирует файл, где будут все эти экстеншены, но с нормальными именами.