前言

前段时间看了阿黄哥的一篇介绍Compose for ios 的文章

Compose跨渠道第三弹:体验Compose for iOS(黄林晴)

/post/719577…

才知道 compose 现已能够跨ios端了,自己也计划上手试试。等自己实践上手之后,才发现有许多坑,尤其是装备环境方面,所以计划写一篇文章记录一下。

开端

接下来,我会从新建一个项目,依靠装备,环境装备,一直到能够运用Compose 写一个Hello WordDemo, 这样的过程来介绍一个我曾遇见的坑以及解决方法。

假如要测验ios方面的东西,一定是需求mac系统的,无论是用mac 电脑 还是运用虚拟机,并且还需求Xocde 这个巨无霸。

上手试试 Compose for ios

首先来介绍一个我运用的环境:

  • 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.3Xcode 我是下载的13.2.1,直接下载最新版的Xcode14 应该也能够。这是关于环境版别需求注意的一些点。

现在开端正式的建立项目。

首先要装置一个Android Studio 插件,直接 在插件商场查找 Kotlin Multiplatform Mobile 就能够。

装置完成之后,在新建项目的时分 就能够看到在最终多出来两个项目模板,

上手试试 Compose for ios

这儿运用第一个模板。

创立出来目录结构大概是这个姿态的:

上手试试 Compose for ios

  • androidApp便是运行在Android渠道上的。
  • iosApp 便是运行在ios渠道上的。
  • shared便是两者通用的部分。

shared 中又分为androidMainiosMaincommonMain 三个部分。

主要是在commonMain中界说行为,然后别离在androidMainiosMain 中别离完成,这个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…

上手试试 Compose for ios

​ 引证自: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

上手试试 Compose for ios

有时分可能因为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

这儿是需求在sharebuild.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.

这个是在sharebuild.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
}
}

最终来看看作用:

上手试试 Compose for ios

用来演示作用的代码十分简略,便是运用一个Text 组件来展现 设备的类型。

写在最终

自己在上手的时分本认为很简略,成果便是不断踩坑,一起ios相关知识也比较匮乏,有些问题的解决方案网上的答案也十分少,最终是四五天才干正常跑起来这个Demo。现在是将这些坑都记录下来,希望能给其他的同学能够供给一些帮助。

后面的计划会测验运用ktor接入一些网络请求,然后写一个跨端的开源项目,假如再遇见什么坑会持续共享这个踩坑系列。

关于Compose for Desktop 之前也有过测验,是写了一个adb GUI的工具项目,十分简略 ,没遇见什么坑,便是Compose的约束布局没有。目前工作中常常用到的一些工具 也是运用Compose写的。

今天的碎碎念就到这儿了,这次写的也比较细,比较碎,我们要是有什么问题,欢迎一起交流。