上手试试 Compose for ios
前言
前段时间看了阿黄哥的一篇介绍Compose for ios
的文章
Compose跨平台第三弹:体验Compose for iOS(黄林晴)
才知道 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
中主要是展示系统版本。
interface Platform {
val name: String
}
expect fun getPlatform(): Platform
expect
关键字是将此声明标记为是平台相关的,并期待从模块中实现。
然后在对应模块中实现:
//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 : 表示多平台项目中的一个平台相关实现
Kotlin关键字 参见:
http://www.kotlincn.net/docs/refere…
引用自:http://www.kotlincn.net/docs/refere…
这个模板项目大概就了解这么多,接下我们开始引入Compose
相关依赖。
首先在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()
// 添加这个
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)
}
}
}
....
}
然后再进行编译的时候,这里会报错:
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
的时候,可以选择直接从这列选择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)" 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.
出现这个错误是需要在gradle.properties
中添加:
kotlin.native.cacheKind=none
然后编译出现了一个报错信息巨多的异常:
//只粘贴了最主要的一个异常信息
Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld invocation reported errors
这里是需要在share
的build.gradle.kts
中加上这个配置:
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).
* 指定框架是否作为静态库链接(默认情况下为false)。
*/
var isStatic = false
然后再编译的时候还遇到一个异常信息:
//org.gradle.api.UnknownDomainObjectException: KotlinTarget with name 'uikitX64' not found.
这个是在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
中写一个Composable
,用来供给两端调用:
@Composable
internal fun KMMComposeView(device:String){
Box(contentAlignment = Alignment.Center) {
Text("Compose跨端 $device view")
}
}
这里一定要写上internal
关键字,internal
是将一个声明 标记为在当前模块可见。
internal 官方文档
http://www.kotlincn.net/docs/refere…
不然在ios
调用定义好的Compose
的时候产生下面的异常:
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)
然后再两端添加各自的调用:
androidMain的 Platform.kt
@Composable
fun MyKMMView(){
KMMComposeView("Android")
}
iosMain的 Platform.kt
fun MyKMMView(): UIViewController = Application("ComposeMultiplatformApp") {
KMMComposeView(UIDevice.currentDevice.systemName())
}
最后在androidApp module
中直接调用 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)
//主要关注这一行, 这里是调用我们自己在iosMain里面定义的 KMMViewController
let mainViewController = PlatformKt.KMMViewController()
myWindow?.rootViewController = mainViewController
myWindow?.makeKeyAndVisible()
return true
}
func application(
_ application: UIApplication,
supportedInterfaceOrientationsFor supportedInterfaceOrientationsForWindow: UIWindow?
) -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.all
}
}
最后来看看效果:
用来演示效果的代码非常简单,就是使用一个Text 组件来展示 设备的类型。
写在最后
自己在上手的时候本以为很简单,结果就是不断踩坑,同时ios
相关知识也比较匮乏,有些问题的解决方案网上的答案也非常少,最后是四五天才能正常跑起来这个Demo。现在是将这些坑都记录下来,希望能给其他的同学能够提供一些帮助。
后面的计划会尝试使用ktor接入一些网络请求,然后写一个跨端的开源项目,如果再遇见什么坑会继续分享这个踩坑系列。
关于Compose for Desktop 之前也有过尝试,是写了一个adb GUI的工具项目,非常简单 ,没遇见什么坑,就是Compose的约束布局没有。目前工作中经常用到的一些工具 也是使用Compose写的。
今天的碎碎念就到这里了,这次写的也比较细,比较碎,大家要是有什么问题,欢迎一起交流。
链接:https://juejin.cn/post/7201831630603976741
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。