上手試試 Compose for ios

前言

前段時(shí)間看了阿黃哥的一篇介紹Compose for ios 的文章

Compose跨平臺第三彈:體驗(yàn)Compose for iOS(黃林晴)

juejin.cn/post/719577…

才知道 compose 已經(jīng)可以跨ios端了拓哺,自己也打算上手試試棒卷。等自己實(shí)際上手之后缩膝,才發(fā)現(xiàn)有很多坑蔽介,尤其是配置環(huán)境方面,所以打算寫一篇文章記錄一下。

開始

接下來,我會(huì)從新建一個(gè)項(xiàng)目,依賴配置发皿,環(huán)境配置,一直到可以使用Compose 寫一個(gè)Hello WordDemo, 這樣的步驟來介紹一個(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奖唯,iosMaincommonMain 三個(gè)部分。

主要是在commonMain中定義行為缀辩,然后分別在androidMainiosMain 中分別實(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)鍵字 參見:

www.kotlincn.net/docs/refere…

引用自:www.kotlincn.net/docs/refere…

這個(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

參見:github.com/JetBrains/c…

然后編譯出現(xiàn)了一個(gè)報(bào)錯(cuò)信息巨多的異常:

//只粘貼了最主要的一個(gè)異常信息
Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld invocation reported errors

這里是需要在sharebuild.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è)是在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中寫一個(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 官方文檔

www.kotlincn.net/docs/refere…

不然在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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市抬驴,隨后出現(xiàn)的幾起案子炼七,更是在濱河造成了極大的恐慌,老刑警劉巖布持,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件豌拙,死亡現(xiàn)場離奇詭異,居然都是意外死亡题暖,警方通過查閱死者的電腦和手機(jī)按傅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芙委,“玉大人逞敷,你說我怎么就攤上這事」嗦拢” “怎么了推捐?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長侧啼。 經(jīng)常有香客問我牛柒,道長堪簿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任皮壁,我火速辦了婚禮椭更,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蛾魄。我一直安慰自己虑瀑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布滴须。 她就那樣靜靜地躺著舌狗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扔水。 梳的紋絲不亂的頭發(fā)上痛侍,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音魔市,去河邊找鬼主届。 笑死,一個(gè)胖子當(dāng)著我的面吹牛待德,可吹牛的內(nèi)容都是我干的君丁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼磅网,長吁一口氣:“原來是場噩夢啊……” “哼谈截!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起涧偷,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎毙死,沒想到半個(gè)月后燎潮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扼倘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年确封,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片再菊。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡爪喘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出纠拔,到底是詐尸還是另有隱情秉剑,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布稠诲,位于F島的核電站侦鹏,受9級特大地震影響诡曙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜略水,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一价卤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧渊涝,春花似錦慎璧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至煤傍,卻和暖如春盖文,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蚯姆。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工五续, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人龄恋。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓疙驾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親郭毕。 傳聞我的和親對象是個(gè)殘疾皇子它碎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容