前言
前段時(shí)間看了阿黃哥的一篇介紹Compose for ios
的文章
Compose跨平臺第三彈:體驗(yàn)Compose for iOS(黃林晴)
才知道 compose
已經(jīng)可以跨ios
端了拓哺,自己也打算上手試試棒卷。等自己實(shí)際上手之后缩膝,才發(fā)現(xiàn)有很多坑蔽介,尤其是配置環(huán)境方面,所以打算寫一篇文章記錄一下。
開始
接下來,我會(huì)從新建一個(gè)項(xiàng)目,依賴配置发皿,環(huán)境配置,一直到可以使用Compose
寫一個(gè)Hello Word
的Demo,
這樣的步驟來介紹一個(gè)我曾遇見的坑以及解決方法拂蝎。
如果要嘗試ios
方面的東西穴墅,一定是需要mac
系統(tǒng)的,無論是用mac
電腦 還是使用虛擬機(jī)温自,并且還需要Xocde
這個(gè)巨無霸玄货。
首先來介紹一個(gè)我使用的環(huán)境:
mac os 12.6.3
Xcode 13.2.1
Kotlin 1.8.0
Compsoe 1.3.0
我之前研究過KMM
,曾嘗試寫了一個(gè)Demo
悼泌,當(dāng)時(shí)的mac
系統(tǒng)是 10.17
版本松捉,最多只能下載Xcode 12.3,而這個(gè)版本的Xcode
只能編譯 Kotlin 1.5.31,想要用高版本的kotlin
就得需要使用 12.5版本的Xcdoe
馆里,所以我就是直接將mac
系統(tǒng)升級到了 12.6.3
隘世,Xcode
我是下載的13.2.1
可柿,直接下載最新版的Xcode14 應(yīng)該也可以。這是關(guān)于環(huán)境版本需要注意的一些點(diǎn)丙者。
現(xiàn)在開始正式的搭建項(xiàng)目复斥。
首先要安裝一個(gè)Android Studio
插件,直接 在插件市場搜索 Kotlin Multiplatform Mobile 就可以械媒。
安裝完成之后目锭,在新建項(xiàng)目的時(shí)候 就可以看到在最后多出來兩個(gè)項(xiàng)目模板,
這里使用第一個(gè)模板纷捞。
創(chuàng)建出來目錄結(jié)構(gòu)大概是這個(gè)樣子的:
-
androidApp
就是運(yùn)行在Android
平臺上的痢虹。 -
iosApp
就是運(yùn)行在ios
平臺上的。 -
shared
就是兩者通用的部分主儡。
shared
中又分為androidMain
奖唯,iosMain
和 commonMain
三個(gè)部分。
主要是在commonMain
中定義行為缀辩,然后分別在androidMain
和iosMain
中分別實(shí)現(xiàn)臭埋,這個(gè)Demo
中主要是展示系統(tǒng)版本。
interface Platform {
val name: String
}
expect fun getPlatform(): Platform
expect
關(guān)鍵字是將此聲明標(biāo)記為是平臺相關(guān)的臀玄,并期待從模塊中實(shí)現(xiàn)。
然后在對應(yīng)模塊中實(shí)現(xiàn):
//android
class AndroidPlatform : Platform {
override val name: String = "Android ${android.os.Build.VERSION.SDK_INT}"
}
actual fun getPlatform(): Platform = AndroidPlatform()
//ios
class IOSPlatform: Platform {
override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}
actual fun getPlatform(): Platform = IOSPlatform()
actual : 表示多平臺項(xiàng)目中的一個(gè)平臺相關(guān)實(shí)現(xiàn)
Kotlin關(guān)鍵字 參見:
這個(gè)模板項(xiàng)目大概就了解這么多畅蹂,接下我們開始引入Compose
相關(guān)依賴健无。
首先在settings.gradle.kts
中添加倉庫:
pluginManagement {
repositories {
google()
gradlePluginPortal()
mavenCentral()
//添加這兩行
maven(uri("https://plugins.gradle.org/m2/")) // For kotlinter-gradle
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
// 添加這個(gè)
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
}
然后引入插件和依賴:
根目錄build.gradle.kts
plugins {
//trick: for the same plugin versions in all sub-modules
id("com.android.application").version("7.4.1").apply(false)
id("com.android.library").version("7.4.1").apply(false)
kotlin("android").version("1.8.0").apply(false)
kotlin("multiplatform").version("1.8.0").apply(false)
//添加此行
id("org.jetbrains.compose") version "1.3.0" apply false
}
share Module 的 build.gradle.kts
plugins {
kotlin("multiplatform")
id("com.android.library")
//添加此行
id("org.jetbrains.compose")
}
sourceSets {
val commonMain by getting{
//添加依賴
dependencies {
with(compose) {
implementation(ui)
implementation(foundation)
implementation(material)
implementation(runtime)
}
}
}
....
}
然后再進(jìn)行編譯的時(shí)候,這里會(huì)報(bào)錯(cuò):
ERROR: Compose targets '[uikit]' are experimental and may have bugs!
But, if you still want to use them, add to gradle.properties:
org.jetbrains.compose.experimental.uikit.enabled=true
這里是需要在gradle.properties
中添加:
org.jetbrains.compose.experimental.uikit.enabled=true
然后在編譯ios module
的時(shí)候液斜,可以選擇直接從這列選擇iosApp
:
有時(shí)候可能因?yàn)閄code環(huán)境問題累贤,這里的iosApp
會(huì)標(biāo)記著一個(gè)紅色的x號,提示找不到設(shè)別少漆,或者其他關(guān)于Xcode
的問題像啼,此時(shí)可以直接點(diǎn)擊iosApp module
下的 iosApp.xcodeproj
猖闪,可以直接用Xcode
來打開編譯。還可以直接直接跑 linkDebugFrameworkIosX64
這個(gè)task 來直接編譯。
此時(shí)編譯我是碰見了一個(gè)異常:
e: Module "org.jetbrains.compose.runtime:runtime-saveable (org.jetbrains.compose.runtime:runtime-saveable-uikitx64)" has a reference to symbol androidx.compose.runtime/remember|-2215966373931868872[0]. Neither the module itself nor its dependencies contain such declaration.
This could happen if the required dependency is missing in the project. Or if there is a dependency of "org.jetbrains.compose.runtime:runtime-saveable (org.jetbrains.compose.runtime:runtime-saveable-uikitx64)" that has a different version in the project than the version that "org.jetbrains.compose.runtime:runtime-saveable (org.jetbrains.compose.runtime:runtime-saveable-uikitx64): 1.3.0" was initially compiled with. Please check that the project configuration is correct and has consistent versions of all required dependencies.
出現(xiàn)這個(gè)錯(cuò)誤是需要在gradle.properties
中添加:
kotlin.native.cacheKind=none
然后編譯出現(xiàn)了一個(gè)報(bào)錯(cuò)信息巨多的異常:
//只粘貼了最主要的一個(gè)異常信息
Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld invocation reported errors
這里是需要在share
的build.gradle.kts
中加上這個(gè)配置:
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach {
it.binaries.framework {
baseName = "shared"
//加上此行
isStatic = true
}
}
/**
* Specifies if the framework is linked as a static library (false by default).
* 指定框架是否作為靜態(tài)庫鏈接(默認(rèn)情況下為false)旗扑。
*/
var isStatic = false
然后再編譯的時(shí)候還遇到一個(gè)異常信息:
//org.gradle.api.UnknownDomainObjectException: KotlinTarget with name 'uikitX64' not found.
這個(gè)是在share
的build.gradle.kts
中加上如下配置:
kotlin{
val args = listOf(
"-linker-option", "-framework", "-linker-option", "Metal",
"-linker-option", "-framework", "-linker-option", "CoreText",
"-linker-option", "-framework", "-linker-option", "CoreGraphics"
)
//org.gradle.api.UnknownDomainObjectException: KotlinTarget with name 'uikitX64' not found.
iosX64("uikitX64") {
binaries {
executable {
entryPoint = "main"
freeCompilerArgs = freeCompilerArgs + args
}
}
}
iosArm64("uikitArm64") {
binaries {
executable {
entryPoint = "main"
freeCompilerArgs = freeCompilerArgs + args
freeCompilerArgs = freeCompilerArgs + "-Xdisable-phases=VerifyBitcode"
}
}
}
}
然后再編譯就可以正常編譯通過了。
下面我們就可以在兩端使用Compose
了眶拉。
首先在commonMain
中寫一個(gè)Composable
筐咧,用來供給兩端調(diào)用:
@Composable
internal fun KMMComposeView(device:String){
Box(contentAlignment = Alignment.Center) {
Text("Compose跨端 $device view")
}
}
這里一定要寫上internal
關(guān)鍵字,internal
是將一個(gè)聲明 標(biāo)記為在當(dāng)前模塊可見脆贵。
internal 官方文檔
不然在ios
調(diào)用定義好的Compose
的時(shí)候產(chǎn)生下面的異常:
Undefined symbols for architecture x86_64:
"_kfun:com.xl.kmmdemo#KMMComposeView(kotlin.String){}", referenced from:
_objc2kotlin_kfun:com.xl.kmmdemo#KMMComposeView(kotlin.String){} in shared(result.o)
然后再兩端添加各自的調(diào)用:
androidMain的 Platform.kt
@Composable
fun MyKMMView(){
KMMComposeView("Android")
}
iosMain的 Platform.kt
fun MyKMMView(): UIViewController = Application("ComposeMultiplatformApp") {
KMMComposeView(UIDevice.currentDevice.systemName())
}
最后在androidApp module
中直接調(diào)用 MyKMMView()
就行了医清,iosApp
想要使用的話,我們還得修改一下iosApp moudle
的代碼:
我們呢需要將 iosApp/iosApp/iOSApp.swift
的原有代碼:
import SwiftUI
@main
struct iOSApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
替換為:
import SwiftUI
import shared
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var myWindow: UIWindow?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
myWindow = UIWindow(frame: UIScreen.main.bounds)
//主要關(guān)注這一行卖氨, 這里是調(diào)用我們自己在iosMain里面定義的 KMMViewController
let mainViewController = PlatformKt.KMMViewController()
myWindow?.rootViewController = mainViewController
myWindow?.makeKeyAndVisible()
return true
}
func application(
_ application: UIApplication,
supportedInterfaceOrientationsFor supportedInterfaceOrientationsForWindow: UIWindow?
) -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.all
}
}
最后來看看效果:
用來演示效果的代碼非常簡單会烙,就是使用一個(gè)Text 組件來展示 設(shè)備的類型负懦。
寫在最后
自己在上手的時(shí)候本以為很簡單,結(jié)果就是不斷踩坑柏腻,同時(shí)ios
相關(guān)知識也比較匱乏密似,有些問題的解決方案網(wǎng)上的答案也非常少,最后是四五天才能正常跑起來這個(gè)Demo『危現(xiàn)在是將這些坑都記錄下來残腌,希望能給其他的同學(xué)能夠提供一些幫助。
后面的計(jì)劃會(huì)嘗試使用ktor接入一些網(wǎng)絡(luò)請求贫导,然后寫一個(gè)跨端的開源項(xiàng)目抛猫,如果再遇見什么坑會(huì)繼續(xù)分享這個(gè)踩坑系列。
關(guān)于Compose for Desktop 之前也有過嘗試孩灯,是寫了一個(gè)adb GUI的工具項(xiàng)目闺金,非常簡單 ,沒遇見什么坑峰档,就是Compose的約束布局沒有败匹。目前工作中經(jīng)常用到的一些工具 也是使用Compose寫的。
今天的碎碎念就到這里了讥巡,這次寫的也比較細(xì)掀亩,比較碎,大家要是有什么問題欢顷,歡迎一起交流槽棍。
作者:不說話的匹諾槽
鏈接:https://juejin.cn/post/7201831630603976741