前言
前段时间看了阿黄哥的一篇介绍Compose for ios
的文章
Compose跨渠道第三弹:体验Compose for iOS(黄林晴)
/post/719577…
才知道 compose
现已能够跨ios
端了,自己也计划上手试试。等自己实践上手之后,才发现有许多坑,尤其是装备环境方面,所以计划写一篇文章记录一下。
开端
接下来,我会从新建一个项目,依靠装备,环境装备,一直到能够运用Compose
写一个Hello Word
的Demo,
这样的过程来介绍一个我曾遇见的坑以及解决方法。
假如要测验ios
方面的东西,一定是需求mac
系统的,无论是用mac
电脑 还是运用虚拟机,并且还需求Xocde
这个巨无霸。
首先来介绍一个我运用的环境:
mac os 12.6.3
Xcode 13.2.1
Kotlin 1.8.0
Compsoe 1.3.0
我之前研究过KMM
,曾测验写了一个Demo
,其时的mac
系统是 10.17
版别,最多只能下载Xcode 12.3,而这个版别的Xcode
只能编译 Kotlin 1.5.31,想要用高版别的kotlin
就得需求运用 12.5版别的Xcdoe
,所以我便是直接将mac
系统升级到了 12.6.3
,Xcode
我是下载的13.2.1
,直接下载最新版的Xcode14 应该也能够。这是关于环境版别需求注意的一些点。
现在开端正式的建立项目。
首先要装置一个Android Studio
插件,直接 在插件商场查找 Kotlin Multiplatform Mobile 就能够。
装置完成之后,在新建项目的时分 就能够看到在最终多出来两个项目模板,
这儿运用第一个模板。
创立出来目录结构大概是这个姿态的:
-
androidApp
便是运行在Android
渠道上的。 -
iosApp
便是运行在ios
渠道上的。 -
shared
便是两者通用的部分。
shared
中又分为androidMain
,iosMain
和 commonMain
三个部分。
主要是在commonMain
中界说行为,然后别离在androidMain
和iosMain
中别离完成,这个Demo
中主要是展现系统版别。
interfacePlatform{
valname:String
}
expectfungetPlatform():Platform
expect
关键字是将此声明标记为是渠道相关的,并等待从模块中完成。
然后在对应模块中完成:
//android
classAndroidPlatform:Platform{
overridevalname:String="Android${android.os.Build.VERSION.SDK_INT}"
}
actualfungetPlatform():Platform=AndroidPlatform()
//ios
classIOSPlatform:Platform{
overridevalname:String=UIDevice.currentDevice.systemName()+""+UIDevice.currentDevice.systemVersion
}
actualfungetPlatform():Platform=IOSPlatform()
actual : 表明多渠道项目中的一个渠道相关完成
Kotlin关键字 拜见:
www.kotlincn.net/docs/refere…
引证自:www.kotlincn.net/docs/refere…
这个模板项目大概就了解这么多,接下咱们开端引入Compose
相关依靠。
首先在settings.gradle.kts
中增加库房:
pluginManagement{
repositories{
google()
gradlePluginPortal()
mavenCentral()
//增加这两行
maven(uri("https://plugins.gradle.org/m2/"))//Forkotlinter-gradle
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
}
dependencyResolutionManagement{
repositories{
google()
mavenCentral()
//增加这个
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
}
然后引入插件和依靠:
根目录build.gradle.kts
plugins{
//trick:forthesamepluginversionsinallsub-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"applyfalse
}
shareModule的build.gradle.kts
plugins{
kotlin("multiplatform")
id("com.android.library")
//增加此行
id("org.jetbrains.compose")
}
sourceSets{
valcommonMainbygetting{
//增加依靠
dependencies{
with(compose){
implementation(ui)
implementation(foundation)
implementation(material)
implementation(runtime)
}
}
}
....
}
然后再进行编译的时分,这儿会报错:
ERROR:Composetargets'[uikit]'areexperimentalandmayhavebugs!
But,ifyoustillwanttousethem,addtogradle.properties:
org.jetbrains.compose.experimental.uikit.enabled=true
这儿是需求在gradle.properties
中增加:
org.jetbrains.compose.experimental.uikit.enabled=true
然后在编译ios module
的时分,能够选择直接从这列选择iosApp
:
有时分可能因为Xcode环境问题,这儿的iosApp
会标记取一个赤色的x号,提示找不到设别,或许其他关于Xcode
的问题,此刻能够直接点击iosApp module
下的 iosApp.xcodeproj
,能够直接用Xcode
来打开编译。还能够直接直接跑 linkDebugFrameworkIosX64
这个task 来直接编译。
此刻编译我是碰见了一个反常:
e:Module"org.jetbrains.compose.runtime:runtime-saveable(org.jetbrains.compose.runtime:runtime-saveable-uikitx64)"hasareferencetosymbolandroidx.compose.runtime/remember|-2215966373931868872[0].Neitherthemoduleitselfnoritsdependenciescontainsuchdeclaration.
Thiscouldhappeniftherequireddependencyismissingintheproject.Orifthereisadependencyof"org.jetbrains.compose.runtime:runtime-saveable(org.jetbrains.compose.runtime:runtime-saveable-uikitx64)"thathasadifferentversionintheprojectthantheversionthat"org.jetbrains.compose.runtime:runtime-saveable(org.jetbrains.compose.runtime:runtime-saveable-uikitx64):1.3.0"wasinitiallycompiledwith.Pleasecheckthattheprojectconfigurationiscorrectandhasconsistentversionsofallrequireddependencies.
出现这个过错是需求在gradle.properties
中增加:
kotlin.native.cacheKind=none
拜见:github.com/JetBrains/c…
然后编译出现了一个报错信息巨多的反常:
//只粘贴了最主要的一个反常信息
Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ldinvocationreportederrors
这儿是需求在share
的build.gradle.kts
中加上这个装备:
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach{
it.binaries.framework{
baseName="shared"
//加上此行
isStatic=true
}
}
/**
*Specifiesiftheframeworkislinkedasastaticlibrary(falsebydefault).
*指定结构是否作为静态库链接(默许情况下为false)。
*/
varisStatic=false
然后再编译的时分还遇到一个反常信息:
//org.gradle.api.UnknownDomainObjectException:KotlinTargetwithname'uikitX64'notfound.
这个是在share
的build.gradle.kts
中加上如下装备:
kotlin{
valargs=listOf(
"-linker-option","-framework","-linker-option","Metal",
"-linker-option","-framework","-linker-option","CoreText",
"-linker-option","-framework","-linker-option","CoreGraphics"
)
//org.gradle.api.UnknownDomainObjectException:KotlinTargetwithname'uikitX64'notfound.
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
中写一个Composable
,用来供给两头调用:
@Composable
internalfunKMMComposeView(device:String){
Box(contentAlignment=Alignment.Center){
Text("Compose跨端$deviceview")
}
}
这儿一定要写上internal
关键字,internal
是将一个声明 标记为在当前模块可见。
internal 官方文档
www.kotlincn.net/docs/refere…
不然在ios
调用界说好的Compose
的时分发生下面的反常:
Undefinedsymbolsforarchitecturex86_64:
"_kfun:com.xl.kmmdemo#KMMComposeView(kotlin.String){}",referencedfrom:
_objc2kotlin_kfun:com.xl.kmmdemo#KMMComposeView(kotlin.String){}inshared(result.o)
然后再两头增加各自的调用:
androidMain的Platform.kt
@Composable
funMyKMMView(){
KMMComposeView("Android")
}
iosMain的Platform.kt
funMyKMMView():UIViewController=Application("ComposeMultiplatformApp"){
KMMComposeView(UIDevice.currentDevice.systemName())
}
最终在androidApp module
中直接调用 MyKMMView()
就行了,iosApp
想要运用的话,咱们还得修正一下iosApp moudle
的代码:
咱们呢需求将 iosApp/iosApp/iOSApp.swift
的原有代码:
importSwiftUI
@main
structiOSApp:App{
varbody:someScene{
WindowGroup{
ContentView()
}
}
}
替换为:
importSwiftUI
importshared
@UIApplicationMain
classAppDelegate:UIResponder,UIApplicationDelegate{
varmyWindow:UIWindow?
funcapplication(
_application:UIApplication,
didFinishLaunchingWithOptionslaunchOptions:[UIApplication.LaunchOptionsKey:Any]?
)->Bool{
myWindow=UIWindow(frame:UIScreen.main.bounds)
//主要重视这一行,这儿是调用咱们自己在iosMain里边界说的KMMViewController
letmainViewController=PlatformKt.KMMViewController()
myWindow?.rootViewController=mainViewController
myWindow?.makeKeyAndVisible()
returntrue
}
funcapplication(
_application:UIApplication,
supportedInterfaceOrientationsForsupportedInterfaceOrientationsForWindow:UIWindow?
)->UIInterfaceOrientationMask{
returnUIInterfaceOrientationMask.all
}
}
最终来看看作用:
用来演示作用的代码十分简略,便是运用一个Text 组件来展现 设备的类型。
写在最终
自己在上手的时分本认为很简略,成果便是不断踩坑,一起ios
相关知识也比较匮乏,有些问题的解决方案网上的答案也十分少,最终是四五天才干正常跑起来这个Demo。现在是将这些坑都记录下来,希望能给其他的同学能够供给一些帮助。
后面的计划会测验运用ktor接入一些网络请求,然后写一个跨端的开源项目,假如再遇见什么坑会持续共享这个踩坑系列。
关于Compose for Desktop 之前也有过测验,是写了一个adb GUI的工具项目,十分简略 ,没遇见什么坑,便是Compose的约束布局没有。目前工作中常常用到的一些工具 也是运用Compose写的。
今天的碎碎念就到这儿了,这次写的也比较细,比较碎,我们要是有什么问题,欢迎一起交流。