KotlinConfの内容を見ていたら、Compose for iOSのアルファ版がリリースされました。
私はAndroidエンジニアですが、ComposeでiOSアプリが作れるのはアツい展開なので早速動かしてみました。
Hello World
上記のリポジトリから「Use this templete」します。
その後環境をチェックしましょう。
Readmeに書かれている通りですが、kdoctorをinstallして環境が整っているかチェックします。
brew install kdoctor kdoctor
動かすにはcocoapodsをインストールする必要があるんですが、M1などAppleシリコンのCPUだとちょっと一手間必要です。 詰まったらこのStackOverflowを見るように言われるのでこちらを参考に解決します。
ios - How to run CocoaPods on Apple Silicon (M1) - Stack Overflow
環境が整ったらAndroid Studioでrun対象をiosAppにしてビルドするとiOSシュミレーターが起動します。
Compose for iOS プロジェクト起動できた!!!!!
— kako351@技術書典14出典 (@kako_351) 2023年4月13日
Android StudioからiOSエミュレーター起動できるの感動するなhttps://t.co/I8ShWBSVfr pic.twitter.com/oYmL95aT4D
少し遊んでみる
起動できたのでちょっとだけ遊んでみます。
このような料理の画像が一覧で見られるアプリを作ってみます。
moko-resourceライブラリの導入
画像を表示するにあたり今回moko-resource
を利用しました。
リソースの扱いはプラットフォームごとに異なるだろうと想像はしていたんですが、その解決方法を探していたところ行きつきました。 Androidでのリソースの扱いと似た要領で、Compose Multiplatform 環境でiOSアプリでも画像を表示できます。
依存関係
まずはversion catalogで各アーティファクトを管理します。
[versions] resourcesVersion = "0.21.2" [libraries] resources = { module = "dev.icerock.moko:resources", version.ref = "resourcesVersion" } resourcesCompose = { module = "dev.icerock.moko:resources-compose", version.ref = "resourcesVersion" } resourcesTest = { module = "dev.icerock.moko:resources-test", version.ref = "resourcesVersion" } resourcesGradlePlugin = { module = "dev.icerock.moko:resources-generator", version.ref = "resourcesVersion" }
次はsetting.gradle.ktsに下記を追加します。
dependencyResolutionManagement { // ... versionCatalogs { create("moko") { from(files("./gradle/libs.versions.toml")) } } }
rootのbuild.gradle.ktsに下記を追加します。
buildscript { repositories { mavenLocal() } dependencies { classpath(moko.resourcesGradlePlugin) } }
最後に sharedのbuild.gradle.ktsに下記を追加します。
plugins { // ... id("dev.icerock.mobile.multiplatform-resources") } sourceSets { val commonMain by getting { dependencies { // ... api(moko.resources) api(moko.resourcesCompose) } } // ... } multiplatformResources { multiplatformResourcesPackage = "com.myapplication.common" }
moko-resourceを利用する場合、もしcocoapods内に次の表記があったら編集するか削除しておくのをお勧めします。
cocoapods {
// extraSpecAttributes["resources"] = "['src/commonMain/resources/**', 'src/iosMain/resources/**']" // この記述がある場合は編集または削除
}
リソースを配置する
今回は画像を配置します。
shared/commonMain/resources
の下にMR/images
ディレクトリを作ります。
この中に配置した画像(ex.sample.png)をコード上で次のように呼び出すことができます。
Image(painter = painterResource(MR.images.sample)
画像のファイル名はiOSで画像を取得するために次のようなフォーマットにする必要があります。
- [filename]@1x.png
- [filename]@2x.png
- [filename]@3x.png
(iOSをやっている方からしたら当たり前なのかも)
画像を表示
画像を表示します
Image( painter = painterResource(MR.images.sample), contentDescription = null )
AndroidだとR.drawable.sample
と書くところですが、moko-resourceだとMR.images.sample
のように書きます。
料理の画像を一覧表示する
moko-リソースの使い方がわかったところでアプリを実装してみます。
モデル
import dev.icerock.moko.resources.ImageResource data class Recipe( val resource: ImageResource, val title: String )
リソースを取得するリポジトリ
リポジトリじゃなくてもいいんですが、慣れているのでリポジトリで実装しています。 APIとか立ててないし、Jsonファイルにもしてないのでハードコーディングしてますがここはご自由に。
class RecipeRepositoryImpl: RecipeRepository{ override fun getRecipes(): List<Recipe> { return listOf( Recipe( title = "焼きそば", resource = MR.images.recipe1 ), Recipe( title = "ハンバーガー", resource = MR.images.recipe2 ), Recipe( title = "鶏チャーシュー", resource = MR.images.recipe3 ), Recipe( title = "豚丼", resource = MR.images.recipe4 ), Recipe( title = "山芋ソテー", resource = MR.images.recipe5 ), Recipe( title = "団子", resource = MR.images.recipe6 ), Recipe( title = "ラーメン", resource = MR.images.recipe7 ), Recipe( title = "豚キムチ", resource = MR.images.recipe8 ), Recipe( title = "唐揚げ", resource = MR.images.recipe9 ), Recipe( title = "コーヒー", resource = MR.images.recipe10 ) ) } }
アイテムを表示するComposable
ちょっとイケてる感の演出のため画像の上にタイトルを被せて表示してみます。
dp
とかsp
と書いてもiOSアプリで起動できるのでスゴイです。
@Composable fun RecipeCell( modifier: Modifier = Modifier, recipe: Recipe ) { Box(modifier) { Image( painter = painterResource(recipe.resource), contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier.fillMaxWidth().aspectRatio(1f) ) Column(modifier = Modifier .fillMaxWidth() .align(Alignment.BottomCenter) .background( Brush.verticalGradient( colors = listOf( Color.Transparent, Color(0x80000000) ) ) ) .padding(vertical = 16.dp, horizontal = 8.dp) ) { Text( text = recipe.title, style = TextStyle(color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.Bold) ) } } }
一覧表示するCompose
@Composable fun GridList(recipes: List<Recipe>) { LazyVerticalGrid(columns = GridCells.Fixed(2)) { items(recipes.size) { index -> RecipeCell(recipe = recipes[index]) } } }
データを取得して一覧表示
@Composable fun App() { MyTheme { val recipes = RecipeRepositoryImpl().getRecipes() Scaffold( topBar = { TopAppBar( title = { Text(text = "Recipe Album") }, ) }, content = { GridList(recipes) } ) } }
これで実行するとスクショのようになります。ほんとうにComposeでiOSアプリが作れます。 kotlinしか書いてないです。
所感
- KMMとの相乗効果もあり、ComposeでiOSアプリが開発できるのはAndroidエンジニアとして作れるモノの広がりを感じる
- 一方でリソースの扱いなど、プラットフォームの知識が必要になるシーンもあるので完全にAndroidのみの知識だけで完結するわけではない
- アルファ版ではあるものの個人開発で使う選択肢の1つになりそう(てかかなりアリ)