【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多平台的内容值得参考?

相关推荐
Ticnix11 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人12 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl12 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅12 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人12 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼12 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端
布列瑟农的星空12 小时前
前端都能看懂的Rust入门教程(三)——控制流语句
前端·后端·rust
Mr Xu_12 小时前
Vue 3 中计算属性的最佳实践:提升可读性、可维护性与性能
前端·javascript
jerrywus12 小时前
我写了个 Claude Code Skill,再也不用手动切图传 COS 了
前端·agent·claude
玖月晴空12 小时前
探索关于Spec 和Skills 的一些实战运用-Kiro篇
前端·aigc·代码规范