【Compose multiplatform教程】05 IOS环境编译

了解如何使现有的 Android 应用程序跨平台,以便它在 Android 和 iOS 上都能运行。您将能够在一个位置编写代码并针对 Android 和 iOS 进行测试一次。

本教程使用一个示例 Android 应用程序,其中包含用于输入用户名和密码的单个屏幕。凭证经过验证并保存到"内存"数据库。

如果您不熟悉 Kotlin Multiplatform,请先了解如何设置环境并从头开始创建跨平台应用程序

Window开发环境

编译Ios环境需要mac xcode,如果您是window

需要安装虚拟机,虚拟机有许多种,这里仅介绍

VMware安装macOS虚拟机详细教程https://www.overwall.info/168.html

vmware虚拟机安装macOS视频教程https://www.bilibili.com/video/BV1PtynYHE13/?spm_id_from=333.337.search-card.all.click&vd_source=36df3adcf294146e4c45aa1b10354537

准备开发环境

  1. 安装所有必要的工具并将它们更新到最新版本https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-setup.html

您需要一台装有 macOS 的 Mac 才能完成本教程中的某些步骤,其中包括编写特定于 iOS 的代码和运行 iOS 应用程序。这些步骤不能在其他操作系统上执行,例如 Microsoft Windows。这是由于 Apple 的要求。

让你的跨平台应用在 iOS 上运行

一旦你使安卓应用具备跨平台特性,就可以创建一个 iOS 应用,并复用其中共享的业务逻辑。

1.在 Xcode 中创建一个 iOS 项目。

2.将框架连接到你的 iOS 项目。

3.从 Swift 使用共享模块。

在 Xcode 中创建 iOS 项目

1.在 Xcode 中,点击 "文件"|"新建"|"项目"。

2.选择一个 iOS 应用模板,然后点击 "下一步"。

3.将产品名称设定为 "simpleLoginIOS" ,然后点击 "下一步"。

4.对于项目的存储位置,选择存放你跨平台应用的目录,比如 "kmp - integration - sample"。

在安卓开发工具(Android Studio)中,你会得到以下结构:

为了与跨平台项目的其他顶级目录保持一致,你可以将 "simpleLoginIOS" 目录重命名为 "iosApp"。要完成这一操作,需先关闭 Xcode,然后把 "simpleLoginIOS" 目录重命名为 "iosApp"。要是在 Xcode 打开的状态下重命名该文件夹,你将会收到警告,而且还有可能损坏项目。

将框架连接到你的 iOS 项目

一旦你有了框架,就可以手动将其连接到你的 iOS 项目中。

另一种方法是通过 CocoaPods,来配置集成,但这种集成方式不在本教程的讲解范围内。

手动将你的框架连接到 iOS 项目:

1.在 Xcode 中,双击项目名称打开 iOS 项目设置。

2.在项目设置的 Build Phases "构建阶段" 选项卡上,点击 "+" 并添加 New Run Script Phase "新的运行脚本阶段"。

3.添加以下脚本:

4.将运行脚本阶段移动到编译源代码阶段之前

5.在"构建设置"选项卡中,在"构建选项"下禁用"用户脚本沙盒"。

这可能需要重新启动你的 Gradle 守护进程,如果你在未先禁用沙盒功能的情况下构建了 iOS 项目。请停止可能已被置于沙盒中的 Gradle 守护进程。

./gradlew --stop

6.在 Xcode 中构建项目。如果一切设置正确,项目将成功构建。

如果你有与默认的 "调试(Debug)" 或 "发布(Release)" 不同的自定义构建配置,在 "构建设置" 选项卡中,在 "用户自定义" 下添加 "KOTLIN_FRAMEWORK_BUILD_TYPE" 设置,并将其设为 "调试(Debug)" 或 "发布(Release)"。

使用 Swift 中的共享模块

1.在 Xcode 中,打开 ContentView.swift 文件并导入共享模块:

Groovy 复制代码
import shared

2.为检查连接是否正常,使用跨平台应用共享模块中的 greet() 函数。

Groovy 复制代码
import SwiftUI
import shared

struct ContentView: View {
    var body: some View {
        Text(Greeting().greet())
        .padding()
    }
}

3.从 Xcode 运行该应用程序以查看结果:

4.在 ContentView.swift 文件中,编写使用共享模块中的数据并渲染应用程序用户界面的代码

Kotlin 复制代码
import SwiftUI
import shared

struct ContentView: View {
    @State private var username: String = ""
    @State private var password: String = ""

    @ObservedObject var viewModel: ContentView.ViewModel

    var body: some View {
        VStack(spacing: 15.0) {
            ValidatedTextField(titleKey: "Username", secured: false, text: $username, errorMessage: viewModel.formState.usernameError, onChange: {
                viewModel.loginDataChanged(username: username, password: password)
            })
            ValidatedTextField(titleKey: "Password", secured: true, text: $password, errorMessage: viewModel.formState.passwordError, onChange: {
                viewModel.loginDataChanged(username: username, password: password)
            })
            Button("Login") {
                viewModel.login(username: username, password: password)
            }.disabled(!viewModel.formState.isDataValid || (username.isEmpty && password.isEmpty))
        }
        .padding(.all)
    }
}

struct ValidatedTextField: View {
    let titleKey: String
    let secured: Bool
    @Binding var text: String
    let errorMessage: String?
    let onChange: () -> ()

    @ViewBuilder var textField: some View {
        if secured {
            SecureField(titleKey, text: $text)
        }  else {
            TextField(titleKey, text: $text)
        }
    }

    var body: some View {
        ZStack {
            textField
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .autocapitalization(.none)
                .onChange(of: text) { _ in
                    onChange()
                }
            if let errorMessage = errorMessage {
                HStack {
                    Spacer()
                    FieldTextErrorHint(error: errorMessage)
                }.padding(.horizontal, 5)
            }
        }
    }
}

struct FieldTextErrorHint: View {
    let error: String
    @State private var showingAlert = false

    var body: some View {
        Button(action: { self.showingAlert = true }) {
            Image(systemName: "exclamationmark.triangle.fill")
                .foregroundColor(.red)
        }
        .alert(isPresented: $showingAlert) {
            Alert(title: Text("Error"), message: Text(error), dismissButton: .default(Text("Got it!")))
        }
    }
}

extension ContentView {

    struct LoginFormState {
        let usernameError: String?
        let passwordError: String?
        var isDataValid: Bool {
            get { return usernameError == nil && passwordError == nil }
        }
    }

    class ViewModel: ObservableObject {
        @Published var formState = LoginFormState(usernameError: nil, passwordError: nil)

        let loginValidator: LoginDataValidator
        let loginRepository: LoginRepository

        init(loginRepository: LoginRepository, loginValidator: LoginDataValidator) {
            self.loginRepository = loginRepository
            self.loginValidator = loginValidator
        }

        func login(username: String, password: String) {
            if let result = loginRepository.login(username: username, password: password) as? ResultSuccess  {
                print("Successful login. Welcome, \(result.data.displayName)")
            } else {
                print("Error while logging in")
            }
        }

        func loginDataChanged(username: String, password: String) {
            formState = LoginFormState(
                usernameError: (loginValidator.checkUsername(username: username) as? LoginDataValidator.ResultError)?.message,
                passwordError: (loginValidator.checkPassword(password: password) as? LoginDataValidator.ResultError)?.message)
        }
    }
}

5.在 simpleLoginIOSApp.swift 文件中,导入共享模块并为 ContentView () 函数指定参数。

Kotlin 复制代码
import SwiftUI
import shared

@main
struct SimpleLoginIOSApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView(viewModel: .init(loginRepository: LoginRepository(dataSource: LoginDataSource()), loginValidator: LoginDataValidator()))
        }
    }
}

6.运行 Xcode 项目,你会看到 iOS 应用显示出登录表单。在用户名处输入 "Jane",密码处输入 "password"。应用会使用共享代码对输入内容进行验证。

享受成果吧 ------ 只需更新一次逻辑。

现在,你的应用程序实现了跨平台。你只需在一处更新业务逻辑,就能在安卓和 iOS 系统上看到相应的变化。

1.在 Android Studio 中,更改用户密码的验证逻辑:"password" 不应该是一个有效的选项。为此,更新 LoginDataValidator 类的 checkPassword () 函数:

Kotlin 复制代码
package com.jetbrains.simplelogin.shared.data

class LoginDataValidator {
//...
    fun checkPassword(password: String): Result {
        return when {
            password.length < 5 -> Result.Error("Password must be >5 characters")
            password.lowercase() == "password" -> Result.Error("Password shouldn't be \"password\"")
            else -> Result.Success
        }
    }
//...
}

2.在 Android Studio 中,为 iOS 应用添加运行配置:

在主菜单中选择 "运行(Run)| 编辑配置(Edit configurations)"。

要添加新配置,点击加号,然后选择 "iOS 应用程序(iOS Application)"。

将该配置命名为 "SimpleLoginIOS"。

在 "Xcode 项目(Xcode project)" 文件字段中,选择 simpleLoginIOS.xcodeproj 文件的位置。

在 "执行目标(Execution target)" 列表中选择一个模拟环境,然后点击 "确定(OK)"

3.从 Android Studio 运行 iOS 和安卓应用,查看相应变化。

你可以查看本教程的最终代码。最终代码https://github.com/Kotlin/kmp-integration-sample/tree/final.

还有什么别的可以分享呢?

你已经共享了应用程序的业务逻辑,但你也可以决定共享应用程序的其他层。例如,ViewModel 类的代码在安卓和 iOS 应用中几乎相同,如果你的移动应用需要有相同的表示层,那么你可以共享这部分代码。

接下来做什么?

一旦你让安卓应用实现跨平台,就可以继续进行以下操作:

添加对多平台库的依赖

添加安卓依赖

添加 iOS 依赖

你还可以查看社区资源:

视频:如何将安卓项目迁移至 Kotlin 多平台

视频:让 Kotlin JVM 代码适配 Kotlin 多平台的三种方法

如何在Android应用中添加对多平台库的依赖?

有哪些常见的Kotlin多平台库可以添加到Android应用中?

社区资源中还有哪些关于Kotlin多平台的内容值得参考?

相关推荐
哟哟耶耶24 分钟前
improve-gantt-elastic(vue2中甘特图实现与引入)
前端·甘特图
风雨「83」25 分钟前
解决关于Xcode16提交审核报错
ios
maply42 分钟前
npm 方式安装Pyodide 详解
前端·python·npm·node.js·pyodide
千禧年@1 小时前
html辅助标签与样式表
前端·chrome·html
carl.xu1 小时前
ubuntu Android : adb logcat 过滤多个log
android·linux·adb
程序员猪佩琪2 小时前
软考架构师上岸,我用了这些方法
前端·后端·架构
@大迁世界2 小时前
利用 Tree Shaking 提升 React.js 性能
前端·javascript·react.js·前端框架·ecmascript
某公司摸鱼前端2 小时前
React 第三方状态管理库相关 -- Redux & MobX 篇
前端·javascript·react.js·mobx·redux
傻小胖2 小时前
react中hooks之useRef 用法总结
前端·javascript·react.js
夕水2 小时前
SCSS即将废弃`@import`,以前的`@import`该何去何从?
前端·scss