注册

上手试试 Compose for ios

前言


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



Compose跨平台第三弹:体验Compose for iOS(黄林晴)


juejin.cn/post/719577…



才知道 compose 已经可以跨ios端了,自己也打算上手试试。等自己实际上手之后,才发现有很多坑,尤其是配置环境方面,所以打算写一篇文章记录一下。


开始


接下来,我会从新建一个项目,依赖配置,环境配置,一直到可以使用Compose 写一个Hello WordDemo, 这样的步骤来介绍一个我曾遇见的坑以及解决方法。


如果要尝试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.3Xcode 我是下载的13.2.1,直接下载最新版的Xcode14 应该也可以。这是关于环境版本需要注意的一些点。


现在开始正式的搭建项目。


首先要安装一个Android Studio 插件,直接 在插件市场搜索 Kotlin Multiplatform Mobile 就可以。


安装完成之后,在新建项目的时候 就可以看到在最后多出来两个项目模板,



这里使用第一个模板。


创建出来目录结构大概是这个样子的:




  • androidApp就是运行在Android平台上的。
  • iosApp 就是运行在ios平台上的。
  • shared就是两者通用的部分。

shared 中又分为androidMainiosMaincommonMain 三个部分。


主要是在commonMain中定义行为,然后分别在androidMainiosMain 中分别实现,这个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


参见:github.com/JetBrains/c…



然后编译出现了一个报错信息巨多的异常:


//只粘贴了最主要的一个异常信息
Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld invocation reported errors

这里是需要在sharebuild.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.

这个是在sharebuild.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
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册