Compose for 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

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

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

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

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

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

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

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

然后找到 ContentView.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 中添加这个扩展函数即可:

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 文件,在其中直接添加一个函数:

/*** @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 代码中调用这个代码呢?

其实也很简单:

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

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

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

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

import platform.CoreBluetooth.CBCentralManager
import platform.CoreBluetooth.CBManagerAuthorizationAllowedAlways
import platform.CoreBluetooth.CBManagerAuthorizationDenied
import platform.CoreBluetooth.CBManagerAuthorizationNotDetermined
import platform.CoreBluetooth.CBManagerAuthorizationRestrictedinternal 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 中实现:

func changeOrientation(to orientation: UIInterfaceOrientation) {if #available(iOS 16.0, *) {let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowSceneif (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 中点击按钮后需要旋转屏幕时调用这个匿名函数:

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

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

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

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

Main_iosKt.changeScreenOrientation { KotlinInt inif (KotlinInt == 0) {changeOrientation(to: UIInterfaceOrientation.portrait)}else {changeOrientation(to: UIInterfaceOrientation.landscapeLeft)}
}

总结

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/213078.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

渲染(iOS渲染过程解析)

渲染 渲染原理 一个硬核硬件科普视频 CPU和GPU CPU(Central Processing Unit):现代计算机整个系统的运算核心、控制核心,适合串行计算。GPU(Graphics Processing Unit):可进行绘图运算工作的…

安防音频接口选型的高性能国产芯片分析

在人工智能兴起之后,安防市场就成为了其全球最大的市场,也是成功落地的最主要场景之一。对于安防应用而言,智慧摄像头、智慧交通、智慧城市等概念的不断涌现,对于芯片产业催生出海量需求。今天,我将为大家梳理GLOBALCH…

springboot_3.2_freemark_基础环境配置

springboot_3.2_freemark_基础环境配置 一、前言二、环境三、相关资料四、目标五、默认配置项六、构建springboot 3.2项目6.1 pom.xml 内容:6.2 启动类6.3 添加ftlh模板6.4 controller内容6.5 bootstrap.yml配置 七、总结 一、前言 FreeMarker 是一款模板引擎&…

Linux——缓冲区与实现C库的fopen,fwrite,fclose

目录 一.缓冲区 1缓冲区的概念 2.缓冲区存在的意义 3.缓冲区刷新策略 4.什么是刷新? C语言的缓冲区在哪里? ​编辑 仿写C库里的fopen,fclose,fwrite。 mystdio.h mystdio.c main.c(向文件中写入20次msg) 一.缓冲区 1…

b站pwn的学习总结

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

玩转大数据10:深度学习与神经网络在大数据中的应用

目录 1. 引言:深度学习和神经网络在大数据中的重要性和应用场景 2. 深度学习的基本概念和架构 3. Java中的深度学习框架 3.1. Deeplearning4j框架介绍及Java编程模型 3.2. DL4J、Keras和TensorFlow的集成 4. 大数据与深度学习的结合 4.1. 大数据与深度学…

电脑端同时登录多个微信

1、建立一个txt文件 2、右击微信查看应用的属性,记录文件的位置 3、将步骤二得到的路径按照下方的格式输入到步骤一的文本中 4、保存之后将文本后缀名的.txt改成.bat 5、在未登录微信的情况下,双击即可得到两个微信登录窗口

Python高级算法——回溯法(Backtracking)

Python中的回溯法(Backtracking):高级算法解析 回溯法是一种通过尝试所有可能的解来找到问题解的算法设计方法。它通常应用于组合问题、排列问题、子集问题等。在本文中,我们将深入讲解Python中的回溯法,包括基本概念…

解决oracle.sql.TIMESTAMP序列化转换失败问题 及 J2EE13Compliant原理

目录 报错现象报错内容处理方法Oracle驱动源码总结 报错现象 oracle表中存在TIMESTAMP类型的列时,jdbc查出来做序列化时报错 报错内容 org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframewo…

x86和ARM中配置无线网SSID和PASSWORD

提供一个可行的方法 1.准备文件 hostapd.conf :是用户控件的守护进程用于无线接入点(AP)和授权服务器(authentication servers),存放路径:/etc/hostapd/hostapd.conf interfacewlp5s0 drivernl80211 chan…

Java中多线程中 synchronized 锁升级的原理是什么?

Java中多线程中 synchronized 锁升级的原理是什么? 在 Java 中,synchronized 锁的升级是指在不同的场景下,锁的性能优化。Java 的锁有多个状态,主要包括偏向锁、轻量级锁和重量级锁。 偏向锁:当只有一个线程访问同步块…

acwing算法提高之动态规划--背包模型(三)

目录 1 基础知识2 模板3 工程化 1 基础知识 暂无。。。 2 模板 暂无。。。 3 工程化 题目1:潜水员。 解题思路:DP。 状态定义f[i][j][k]:从前i个物品中选,氧气至少为j,氮气至少为k的最小方案数。 状态转移&…

解决idea 通过build project 手动触发热部署失败

在debug运行项目的过程中,并且保证(不添加方法,不修改方法名)一定的规则的情况下,可以通过build project 来手动热部署项目,也就是会交换class文件与resouces文件。 设置项 Edit Configurations Modify Op…

计算机图形学理论(1):建模基础

本系列根据国外一个图形小哥的讲解为本,整合互联网的一些资料,结合自己的一些理解。 场景的组成部分 场景相当于一个或多个模型的集合。模型包含以下内容: 结构描述:几何形状,如顶点、纹理坐标等表面描述&#xff1a…

Vue3中的defineModel

目录 一、vue3的defineModel介绍 二、defineModel使用 (1)在vite.config.js中开启 (2)子组件 (3)父组件 一、vue3的defineModel介绍 为什么要使用到defineModel呢?这里有这样一种场景&…

“快速排序:一种美丽的算法混沌”(1.hoare)

欢迎来到我的博客!在今天的文章中,我将采用一种独特且直观的方式来探讨我们的主题:我会使用一幅图像来贯穿整篇文章的讲解。这幅精心设计的图表不仅是我们讨论的核心,也是一个视觉辅助工具,帮助你更深入地理解和掌握本…

学习深度强化学习---第2部分----RL动态规划相关算法

文章目录 2.1节 动态规划简介2.2节 值函数与贝尔曼方程2.3节 策略评估2.4节 策略改进2.5节 最优值函数与最优策略2.6节 值迭代与策略迭代2.7节 动态规划求解最优策略 本部分视频所在地址:深度强化学习的理论与实践 2.1节 动态规划简介 态规划有两种思路&#xff1…

前端 Web Workers 简介

简介 以前我们总说,JS 是单线程没有多线程,当 JS 在页面中运行长耗时同步任务的时候就会导致页面假死影响用户体验,从而需要设置把任务放在任务队列中;执行任务队列中的任务也并非多线程进行的,然而现在 HTML5 提供了…

App备案、ios备案Bundle ID查询、公钥信息、SHA-1值

App备案、ios备案Bundle ID查询、公钥信息、SHA-1值 Bundle ID这个就不说了,都知道是啥,主要说公钥信息和SHA-1值的获取 打开钥匙串访问,找到当前需要备案App的dis证书,如下: #####右键点击显示简介 #####可以看…

03.仿简道云公式函数实战-QLExpress初探

1. 前言 在上一篇文章中,我们简单介绍了一下表达式引擎,并引出我们的主角QLExpress.在这篇文章中,我们先来一个QLExpress的热身。 2. 初探QLExpress 源码地址:https://github.com/alibaba/qlExpress 笔者下载源码的版本是3.3.…