Compose for iOS:kotlin 与 swift 互操作

news/2024/7/20 9:48:32 标签: ios, kotlin, swift

前言

类似于 Android 上的 compose,在 iOS 上的 compose 同样支持嵌套显示 compose UI 和 swiftUI 或是 uikit 。

但是不同于 Android 原生就是使用 kotlin 作为开发语言,iOS 的开发语言是 swift 或者 object-c 。虽然大多数业务逻辑都可以直接使用 kotlin 实现,但是有时候有些逻辑无法直接使用 kotlin 实现,必须调用 iOS 原生代码,例如关于 iOS 原生平台的 API。

因此,本文将以实际项目为例,说明如何在 Compose for iOS 实现业务逻辑的互操作。

swift__kotlin_7">swift 调用 kotlin

没错,这次又双叒用 calculator-Compose-MultiPlatform 项目举例子,哈哈哈,谁叫我现在手头就这个完整的跨平台项目呢,而且恰好上次移植这个项目支持 iOS 时留下了一些关于 iOS 平台未解决的问题,正好这次一并解决了。

关于这个项目,第一个要解决的问题就是需要监听屏幕的旋转事件,当监听到屏幕旋转时动态的改变当前显示键盘为标准键盘或程序员键盘。

但是监听屏幕旋转属于是 iOS 的平台特有代码,无法直接在 kotlin 中实现,所以只能在 iOS 原生代码中实现监听后,调用 kotlin 代码更改 Compose 界面逻辑。

在开始之前还是得说明一下,毕竟我不是 iOS 开发者,只是 Android 开发,所以对于 iOS 原生代码一窍不通,下文中提到的大多数 iOS 代码都是我从网上 copy 下来修改的,难免会有所错误,各位大佬发现了欢迎指正。

那么,我们正式开始我们的适配之路吧~

首先,我们需要在 iOS 原生代码也就是 swift 中实现对于屏幕旋转事件的监听。

使用 Xcode 打开我们的 Compose MultiPlatform 项目的 iosApp 目录。

然后找到 ContentView.swift 文件并打开,在其中添加下面一些函数:

swift">struct DetectOrientation: ViewModifier { 
    func body(content: Content) -> some View {
        content
            .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
                // 触发屏幕方向改变事件
            }
    }
}

extension View {
    func detectOrientation() -> some View {
        modifier(DetectOrientation())
    }
}

上述代码在 DetectOrientation 中订阅了接收屏幕方向改变事件,然后又定义了一个 detectOrientation 扩展函数,用于将这个订阅函数绑定到特定的 view 中。

接着,我们在实际使用的 view 中添加这个扩展函数即可:

swift">struct ContentView: View {
    var body: some View {
        VStack {
            ComposeView()
                    .ignoresSafeArea(.keyboard) // Compose has own keyboard handler
        }.detectOrientation()
    }
}

上面的 ContentView 即我们实际用于放置 Compose UI 的界面代码,所以我们就把检测屏幕方向改变的扩展函数加到这里。

此时只要 iOS 设备的屏幕方向发生改变就会触发 DetectOrientation 中的事件,所以我们只要在其中调用我们的 kotlin 代码实现更改键盘逻辑即可。

在这里调用 kotlin 代码非常简单。

我们将 IDE 切换回 AndroidStudio,并打开项目的 shared 模块的 iosMain 包下的 main.ios.kt 文件,在其中直接添加一个函数:

kotlin">/**
 * @param orientation 0 竖,1 横
 * */
fun onScreenChange(orientation: Int) {
    if (orientation == 0) {
        homeChannel?.trySend(
            HomeAction.OnScreenOrientationChange(
                changeToType = KeyboardTypeStandard
            )
        )
    }
    else {
        homeChannel?.trySend(
            HomeAction.OnScreenOrientationChange(
                changeToType = KeyboardTypeProgrammer
            )
        )
    }
}

这个函数逻辑也很简单,接收一个参数 orientation 当其为 0 时表示切换到标准键盘,为 1 时表示切换到程序员键盘。

在这个函数被调用后会发送一个 Action 通知 Compose 更改布局。

那么,怎么在刚才的 swift 代码中调用这个代码呢?

其实也很简单:

swift">struct DetectOrientation: ViewModifier {
    func body(content: Content) -> some View {
        content
            .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
                // 触发屏幕方向改变事件
                if (UIDevice.current.orientation.isLandscape) {
                    Main_iosKt.onScreenChange(orientation: 1)
                }
                else {
                    Main_iosKt.onScreenChange(orientation: 0)
                }
            }
    }
}

没错,就是这么简单,直接 Main_iosKt.onScreenChange() 就可以了。

需要注意的是,这里的这个 onScreenChange 大概率会报错,不用害怕,重新编译一下即可。

这是因为在 iosMain 包下的 kt 函数都会被直接编译成 iOS 的 native 代码,并通过 shared 映射给 iOS ,所以直接调用即可。

kotlin__swift_117">kotlin 调用 swift

其实大多数的业务逻辑已经完全可以直接使用 kotlin 来编写而无需调用 swift 了,除了一些平台特定 API 除外。

此时又有了两种解决方案,一种是 kotlin MultiPlatform 已经封装了大多数的 iOS 平台特定代码到 kotlin 中,我们直接调用即可。

例如关于蓝牙操作的 API 就封装在了 platform.CoreBluetooth 包中,我们需要使用 iOS 的蓝牙时只需要在 kotlin 中导入这个包然后使用即可,例如申请蓝牙权限:

kotlin">import platform.CoreBluetooth.CBCentralManager
import platform.CoreBluetooth.CBManagerAuthorizationAllowedAlways
import platform.CoreBluetooth.CBManagerAuthorizationDenied
import platform.CoreBluetooth.CBManagerAuthorizationNotDetermined
import platform.CoreBluetooth.CBManagerAuthorizationRestricted

internal class BluetoothPermissionDelegate : PermissionDelegate {
    override fun getPermissionState(): PermissionState {
        return when (CBCentralManager.authorization) {
            CBManagerAuthorizationNotDetermined -> {
                // 未授予权限
            }
            CBManagerAuthorizationAllowedAlways, CBManagerAuthorizationRestricted -> {
                // 权限已授予
            }
            CBManagerAuthorizationDenied -> {
                // 权限已被拒绝
            }
            else -> {
                // 其他
            }
        }
    }

    override suspend fun providePermission() {
        CBCentralManager().authorization()
    }

    override fun openSettingPage() {
        // 打开设置界面
    }
}

只是,虽然 kotlin MultiPlatform 已经封装了大多数的平台特定 API ,但是还是会有一些没有封装到的,我们不得不只能通过调用 swift 来使用的 API 。

例如,上文中我们提到了目前项目中移植到 iOS 缺失的部分是关于屏幕方向改变监听的,其实与之对应的还缺失了直接强制更改当前屏幕方向的代码。

因为在程序中不仅支持旋转屏幕切换键盘类型,也支持直接点击切换按钮切换键盘类型,但是只切换类型而不强制旋转屏幕的话 UI 将会变得非常奇怪,所以就必须在更改 UI 的同时更改屏幕方向。

而更改屏幕方向的 API 显然在 kotlin 中并不存在,所以只能我们自己在 swift 中实现:

swift">func changeOrientation(to orientation: UIInterfaceOrientation) {
    if #available(iOS 16.0, *) {

        let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene

        if (orientation.isPortrait) {
            windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait))
        }
        else {
            windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: .landscape))
        }
    }
    else {
        UIDevice.current.setValue(orientation.rawValue, forKey: "orientation")
    }
}

在上述的 swift 代码中我们还对旋转屏幕做了一个适配,因为在 iOS 16 以后,原本直接使用 UIDevice.current.setValue 设置屏幕方向的方法被弃用了,所以需要额外适配一下。

那么,现在在 swift 中旋转屏幕的代码已经有了,该怎么从 kotlin 中调用呢?

答案是,我不知道,我找了很久的资料,也没找到怎么从 kt 中直接调用 swift 函数的方法,也许就是不支持吧。

但是,别慌,虽然没有直接支持的方法,但是我们可以曲线支持。

即然上文我们已经知道了 swift 可以直接调用 kt 函数,而且最重要的是,kt 和 swift 都支持匿名函数以及把匿名函数作为函数的参数。

那么,答案这不就出来了吗?

我们首先在 kt 中定义一个匿名函数: var changeScreenOrientationFunc: ((to: Int) -> Unit)? = null

然后在 Compose 中点击按钮后需要旋转屏幕时调用这个匿名函数:

kotlin">fun changeKeyBoardType(changeTo: Int, isFromUser: Boolean) {
    if (changeTo == KeyboardTypeStandard) {
        changeScreenOrientationFunc?.invoke(0)
    }
    else {
        changeScreenOrientationFunc?.invoke(1)
    }
}

接下来,我们需要在 kt 中定义一个函数用于设置这个匿名函数,然后提供给 swift 调用:

kotlin">fun changeScreenOrientation(callBack: (to: Int) -> Unit) {
    changeScreenOrientationFunc = callBack
}

最后,我们只需要在 swift 中初始化时调用这个函数设置相应的匿名函数实现即可:

swift">Main_iosKt.changeScreenOrientation { KotlinInt in
    if (KotlinInt == 0) {
        changeOrientation(to: UIInterfaceOrientation.portrait)
    }
    else {
        changeOrientation(to: UIInterfaceOrientation.landscapeLeft)
    }
}

总结

以上就是在 compose iOS 中 swiftkotlin 互操作的全部内容,完整代码可见 calculator-Compose-MultiPlatform 项目。

本来今天是准备写在 kotlin jvm 平台调用 jni 实现和 c/c++ 的互操作的,但是遇到一点啸问题,忙活了一整天都没解决,所以就临时改为写一篇 compose iOS 中 swiftkotlin 互操作了。


http://www.niftyadmin.cn/n/5253432.html

相关文章

LeetCode力扣每日一题(Java):28、找出字符串中第一个匹配项的下标

别问我为什么今天做了两题,问就是我干概率论干废了,需要换换脑子想想不同类型的问题,所以来刷刷算法 一、题目 二、解题思路 1、我的思路 其实这题思路还挺简单的,我直接把代码放这,大家应该稍微看看就能懂 char[]…

b站pwn的学习总结

写的很乱 1.c语言的运行过程 了解了c语言需要经过以上2个过程(编译和汇编),才能让机器按指令运行。机器只能听得懂机器码,所以要“汇编”。 那问题就来了,“编译”这个动作有啥用,c语言这种高级语言&…

人工智能原理复习

绪论 人工智能原理复习–绪论 知识表示 人工智能原理复习–知识表示(一) 人工智能原理复习–知识表示(二) 确定性推理 人工智能原理复习–确定性推理 不确定性推理 人工智能原理复习–不确定推理 搜索策略 人工智能原理复…

perl处理base64、md5、SHA-1、SHA-256的计算

使用perl可以进行base64、md5、SHA-1、SHA-256的计算,使用也非常方便,下面是示例代码: #! /usr/bin/perl use v5.14; use MIME::Base64; use Digest;my $test_str hello world;# 测试base64 say encode_base64($test_str);# 测试md5 my $md…

Low Cost and High Performance FPGA with ARM and SDRAM inside

AG10KSDE176 AGM AG10KSDE176 是由 AGM FPGA AG10K 与 SDRAM 叠封集成的芯片,具有 AG10K FPGA 的可编程功能,提供更多可编程 IO,同时内部连接大容量 SDRAM。  FPGA 外部管脚输出 EQFP176 封装底部 Pad 为 GND,管脚说明请见下表&…

Mysql研学-SQL语言基础

一 DDL语句:数据定义语言 1 作用:操作表 2 常用关键字 CREATE(创建) DROP(删除(销毁)) ALTER修改 3 格式 -- 单行注释/* 多行注释 */删除表:DROP TABLE [IF EXISTS] 表名;括号内为查看该表是否存在有则删创建表:参考JavaBean中定义有参构造器 (最后一列不写逗…

Proteus仿真--射击小游戏仿真设计

本文介绍基于proteus射击小游戏仿真设计(完整仿真源文件及代码见文末链接) 仿真图如下 K1-K4为4个按键,用于上移、下移、确认等,模拟单机游戏 仿真运行视频 Proteus仿真--射击小游戏仿真设计 附完整Proteus仿真资料代码资料 …

深入了解 AMD 和 CMD 规范:探索模块加载的世界(上)

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云…