iOS용 Jetpack Compose

Feb 13 2023
시작하기, 단계별
2023년 2월 13일에 업데이트됨 코드: https://github.
Unsplash에 있는 Maxime Horlaville의 사진

2023년 2월 13일 업데이트됨

암호:

https://github.com/marenovakovic/compose-ios

Jetpack Compose는 틀림없이 Android 자체가 시작된 이래로 Android 개발에 일어난 가장 크고 좋은 일입니다. Kotlin을 채택하면 바로 거기에 도달할 수 있습니다. 엔지니어링 대리석과 사용의 절대적인 기쁨입니다.

Compose를 멀티플랫폼으로 포팅하려는 Jetbrains의 노력으로 단일 코드 베이스에서 Android 및 데스크톱 앱을 빌드하는 흥미로운 용도를 볼 수 있습니다. 그러나 iOS는 어떻습니까? 그것이 가능하다는 것이 밝혀졌습니다… 비록 그렇게 부드럽지는 않지만. 이제 어떻게 하는지 단계별로 보여드리겠습니다.

전제 조건

  • 뻔하지만 주목할 가치가 있습니다. MacOS에서만 이 작업을 수행할 수 있습니다. 예, Compose이지만 여전히 시뮬레이터와 같은 것이 필요합니다.
  • 아키텍처 구성요소, 동반 라이브러리 등 Android 개발에서 사용했던 모든 라이브러리를 사용하지 못할 수도 있습니다. 멀티플랫폼(아직?)이 아니기 때문입니다. 하지만 대부분의 경우 Compose는 예상대로 작동합니다. iOS 지원이 아직 실험 단계이기 때문입니다 .

Compose iOS 앱용 모듈이 필요합니다. iOS 앱이 프로젝트의 유일한 모듈인 독립 실행형 앱이거나(네이티브 Android 개발을 위한 방법과 동일) KMM 프로젝트에 모듈을 하나 더 추가할 수 있습니다.

이에 대한 새 프로젝트를 생성하려면 IntelliJ IDEA의 새 프로젝트 마법사에서 Compose Multiplatform을 선택합니다.

새로 생성된 모듈/프로젝트 내에서 이 요지를 복사하여 붙여넣고 이 요지를 에 build.gradle.kts사용 합니다 .settings.gradle.kts

에 대해 설명할 몇 가지 사항입니다 build.gradle.kts.

id("ord.jetbrains.compose") version "1.3.0-rc01, 7행 및 maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")16행은 Compose Multiplatform을 앱에 추가하는 방법입니다.

다음과 같이 대상을 정의합니다.

iosX64("uikitX64") {
    binaries {
        executable {
            entryPoint = "main"
            freeCompilerArgs += listOf(
                "-linker-option", "-framework", "-linker-option", "Metal",
                "-linker-option", "-framework", "-linker-option", "CoreText",
                "-linker-option", "-framework", "-linker-option", "CoreGraphics"
            )
        }
    }
}
iosArm64("uikitArm64") {
    binaries {
        executable {
            entryPoint = "main"
            freeCompilerArgs += listOf(
                "-linker-option", "-framework", "-linker-option", "Metal",
                "-linker-option", "-framework", "-linker-option", "CoreText",
                "-linker-option", "-framework", "-linker-option", "CoreGraphics"
            )
            freeCompilerArgs += "-Xdisable-phases=VerifyBitcode"
        }
    }
}

compose.experimental이제 조금 더 설명이 필요한 블록을 살펴보겠습니다 . uikit.applicationiOS 앱을 설정할 수 있습니다. bundleIdPrefix및 와 같은 것을 볼 수 있습니다 projectName. 우리 Android 개발자와 bundleIdPrefix같으며 거의 ​​자명합니다.applicationIdprojectName

이제 deployConfiguration. Gradle 작업으로 iOS 앱을 배포/실행 중입니다. 댓글에서 볼 수 있습니다. 그것들은 iosDeployIPhone13ProDebug및 입니다 iosDeployIPadDebug. 또한 이 작업에서 앱을 실행하려는 장치를 정의합니다. 내부에서 지정하는 이름은 simulator("xxx")해당 구성에 대한 Gradle 작업을 결정하며 이름은 xxx. Gradle 작업은 iosDeployxxxDebug.

당신은 이미 그것이 무엇인지 궁금해 할 것입니다 teamId. 솔직히 말해서 완전히 확신할 수는 없지만 앱의 릴리스 버전을 빌드하는 데 필요하다고 생각합니다. 디버그는 그것 없이도 잘 실행될 것입니다. 이를 위해서는 Apple 개발자 멤버십이 필요합니다. teamId당신은 거기에서 얻을 것이다 .

Gradle 설정은 여기까지입니다. 파일 구조로 이동합시다. 내부에는 module/src생성이 필요 uikitMain/kotlin하고 그 내부에는 main.uikit.kt파일이 필요합니다. 다음과 같아야 합니다.

iosCompose내가 선택한 이름입니다. 다른 것을 선택할 수 있습니다.

내부에 main.uikit.kt다음 코드를 붙여넣습니다.

이것은 최소한의 설정이며, 보고 나면 위협적이지 않습니다. 지금은 앱이 이 요지의 43행에 작성되어 있다는 점을 제외하면 그것에 대해 아무것도 알 필요가 없습니다.

window!!.rootViewController = Application("Your Application") {
            Text("Helo World, from Compose iOS app!!!")
        }

이제 남은 일은 앱을 실행하는 것뿐이다. 그러나 그렇게 빠르지는 않습니다. 한 단계 더 필요합니다. Xcode의 시뮬레이터에서 iOS 앱을 실행한 적이 없는 경우에만 이 작업을 수행해야 합니다.

그리고 그것이 오늘 당신의 임무입니다.

Xcode에서 새 iOS 앱을 만들고 앱을 실행합니다. 인증서가 필요하기 때문에 필요합니다. Xcode를 설치하라는 메시지가 표시되며 login.keychain비밀번호가 필요합니다.

마지막으로 터미널을 열고 ./gradlew iosDeployIPhone13ProDebugAndroid Studio 또는 IntellijIDEA 내부의 Gradle 도구 창에서 이 작업을 실행하거나 찾습니다.

이 접근 방식은 매우 제한적이라는 점을 언급해야 합니다. 사용하려는 모든 항목은 동일한 모듈에 있어야 합니다. @Composable모듈 에서 s를 가져올 수 없었고 플랫폼 간에 s를 shared공유할 수 없습니다 . @Composable우리 모듈은 완전히 독립적이며 iOS 애플리케이션을 위한 프로젝트가 없습니다.

더 좋은 방법이 있습니다…

두 번째 접근법

지금 살펴볼 접근 방식이 훨씬 낫습니다. @Composable플랫폼 간에 s를 재사용할 수 있고 기본 iOS 애플리케이션에 액세스할 수 있습니다.

@Composable이 접근 방식의 경우 모듈 에 모든 s가 있습니다 shared.

이를 위해서는 클래식 Compose Multiplatform 설정이 필요합니다.

  1. settings.gradle.kts
  2. pluginManagement {
        repositories {
            google()
            gradlePluginPortal()
            maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
            mavenCentral()
        }
    }
    

    plugins {
        //trick: for the same plugin versions in all sub-modules
        id("com.android.application").version("8.1.0-alpha04").apply(false)
        id("com.android.library").version("8.1.0-alpha04").apply(false)
        id("org.jetbrains.compose").version("1.3.0").apply(false)
        kotlin("android").version("1.8.0").apply(false)
        kotlin("multiplatform").version("1.8.0").apply(false)
        id("org.jetbrains.kotlin.jvm") version "1.8.0" apply false
    }
    
    allprojects {
        repositories {
            google()
            mavenCentral()
            maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
        }
    }
    

    plugins {
        kotlin("multiplatform")
        kotlin("native.cocoapods")
        id("com.android.library")
        id("org.jetbrains.compose")
    }
    
    kotlin {
        android()
    
        ios()
        iosSimulatorArm64()
    
        cocoapods {
            summary = "Some description for the Shared Module"
            homepage = "Link to the Shared Module homepage"
            version = "1.0"
            ios.deploymentTarget = "14.1"
            podfile = project.file("../ios/Podfile")
            framework {
                baseName = "shared"
                isStatic = true
            }
        }
        
        sourceSets {
            val commonMain by getting {
                dependencies {
                    implementation(compose.ui)
                    implementation(compose.foundation)
                    implementation(compose.material)
                    implementation(compose.runtime)
                }
            }
            val androidMain by getting
            val iosMain by getting {
                dependsOn(commonMain)
            }
            val iosSimulatorArm64Main by getting {
                dependsOn(iosMain)
            }
        }
    }
    
    android {
        namespace = "com.example.compose_ios"
        compileSdk = 33
        defaultConfig {
            minSdk = 24
            targetSdk = 33
        }
        compileOptions {
            sourceCompatibility = JavaVersion.VERSION_1_8
            targetCompatibility = JavaVersion.VERSION_1_8
        }
    }
    

내부는 그것이 정의된 모듈 외부에서 볼 수 없다는 것을 의미합니다. 우리의 경우 s를 shared어떻게 공유할 것입니까 ? 우리는 각각 @Composable파일 main.android.kt과 main.ios.kt내부 shared/androidMain를 가질 것입니다. 거기에서 내부shared/iosMain s를 호출합니다 . 이와 같이: @Composable

  1. shared/commonMain. 이것은 플랫폼 간에 공유할 실제 애플리케이션입니다.
  2. import androidx.compose.foundation.layout.Box
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.material.MaterialTheme
    import androidx.compose.material.Scaffold
    import androidx.compose.material.Text
    import androidx.compose.material.TopAppBar
    import androidx.compose.runtime.Composable
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.Modifier
    
    @Composable
    internal fun ExampleApplication() {
        MaterialTheme {
            Scaffold(
                topBar = { TopAppBar { Text("Hello") } },
                content = {
                    Box(
                        modifier = Modifier.fillMaxSize(),
                        contentAlignment = Alignment.Center,
                    ) {
                        Text("From Compose!")
                    }
                },
            )
        }
    }
    

    import androidx.compose.runtime.Composable
    
    @Composable
    fun Application() {
        ExampleApplication()
    }
    

    import androidx.compose.ui.window.Application
    import platform.UIKit.UIViewController
    
    fun MainViewController(): UIViewController =
        Application("Example Application") {
            ExampleApplication()
        }
    

//iosApp.swift

import SwiftUI
import shared

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
        let mainViewController = Main_iosKt.MainViewController()
        window?.rootViewController = mainViewController
        window?.makeKeyAndVisible()
        return true
    }
}