マルチプラットフォーム構成で、SwiftUIとJetpack Composeを試す
モチベーション
- DroidKaigi2023で、JetpackCompoeに触れたことで、SwiftUIにも触れたくなった
- マルチプラットフォーム構成で、それぞれの特徴を比較しながら試せると理解が深まりそう
何を試すか
ステップ
詳しくはこちらを参照:https://kotlinlang.org/docs/multiplatform-mobile-getting-started.html
- Android Studioで新規プロジェクト -> Kotlin Multiplatformを選択
- プロジェクト構成の設定画面。 iOS用にCocoaPodsの導入可能なようだ。
- 添付画像のような構成でプロジェクトが作成される
- iOSとAndroidで、各種コンポーネントのカタログページを作る
ちなみに今回の記載の内容はAndroidで、androidx.compose.material3:material3
を利用しているので、純粋なJetpackComposeとは少し差分がある。
成果物
できあがったのは、このようなページ
以降は、この成果物を作る上で理解した、それぞれの特徴を書いておく。
OS毎の比較
スタイル設定の方法
JetpackComposeはModifierと呼ばれる修飾用のクラスを渡して設定し、iOSはComponentに対してメソッドチェーンで指定する。
JetpackCompose
Image( painter = image, contentDescription = "Turtle Rock", modifier = Modifier // 画像を円型に切り抜き、サイズを指定 .clip(CircleShape) .size(64.dp) )
SwiftUI
Image("turtlerock") .resizable() .frame(width: 64, height: 64) .clipShape(Circle())
いずれもModifierと呼ばれていて、思想的には似通っていた。
画像 + Shape表示
画像に対して Shapeを指定するには、AndroidはModifier.clip
を使用し、iOSではView.clipShape
を利用する。
デフォルトで、いくつかのクラスが用意されていて抜粋すると以下の通り。
JetpackCompose
- RoundedCornerShape
- 円形のCircleShapeの実態はRoundedCornerShape(50)
- RectangleShape
- CutCornerShape
SwiftUI
- Circle
- RoundedRectangle
- Rectangle
- Capsule
テキスト
テキストに文字スタイルを設定するのは、AndroidはTextView
のstyle属性に指定し、iOSではText.font
を利用する。
JetpackCompose
Text( text = it.name, modifier = Modifier.padding(4.dp), style = MaterialTheme.typography.bodyMedium // 文字スタイル指定 )
SwiftUI
Text("Texts") .font(.largeTitle) // 文字スタイル指定 .padding(8)
各OSに用途毎にプリセットの文字スタイルが用意されおり、AndroidではTypography
クラスに、iOSではFont
の拡張として、実装されている。
ボタン
ボタンは、Androidにはマテリアルデザインに準拠したコンポーネントクラスが用意されているが、iOSはButtonに対して装飾をしていくことになる。
JetpackCompose
Button( modifier = modifier, enabled = enabled, onClick = {}, ) { Text("Primary") } OutlinedButton( modifier = modifier, onClick = {}, enabled = enabled, ) { Text("Secondary") }
SwiftUI
Button(action: {}) { Text("Primary") .padding(10) } .foregroundColor(Color.white) .background(Color("Primary")) .cornerRadius(3) .font(Font.system(size: 12, weight: .bold)) Button(action: { }) { Text("Secondary") .padding(10) .foregroundColor(Color("Primary")) } .overlay( RoundedRectangle(cornerRadius: 3) .stroke(lineWidth: 1) .foregroundColor(Color.black) )
縦横配置
データ量が多いリストを作る時は、JetpackComposeではLazyColumn
、SwiftUIではLazyVStack
やList
を使う設計になっているようだった。
今回は、簡素なページなので、JetpackComposeではColumn
とRow
、SwiftUIではVStack
とHStack
を利用した。
JetpackCompose
Column { // 縦へのコンポーネント配置 // 画像コンポーネント Text(text = "Image", modifier = Modifier.padding(4.dp), style = MaterialTheme.typography.titleMedium) Row { // 横へのコンポーネント配置 Image(painter = image, contentDescription = "Turtle Rock", modifier = Modifier.size(64.dp)) Image( painter = image, contentDescription = "Turtle Rock", modifier = Modifier.clip(RectangleShape).size(64.dp) ) } // ボタンコンポーネント Text(text = "Buttons", modifier = Modifier.padding(4.dp), style = MaterialTheme.typography.titleMedium) Components.ButtonComponents.values().forEach { Row { Text(text = it.name, modifier = Modifier.padding(4.dp), style = MaterialTheme.typography.bodyMedium) PrimaryButton(text = "Primary", modifier = Modifier.padding(4.dp)) PrimaryButton(text = "Primary", modifier = Modifier.padding(4.dp), enabled = false) } ・・・ } }
SwiftUI
VStack(alignment: .leading) { // 縦へのコンポーネント配置 // 画像コンポーネント Text("Image").font(.largeTitle).padding(8) HStack { // 横へのコンポーネント配置 Image("turtlerock") .resizable() .frame(width: 64, height: 64) .clipShape(Rectangle()) case .circle: Image("turtlerock") .resizable() .frame(width: 64, height: 64) .clipShape(Circle()) default: Text("") } } } } } // ボタンコンポーネント Text("Buttons").font(.largeTitle).padding(8) HStack { Button(action: { }) { Text("Primary").padding(10) } .foregroundColor(Color.white) .background(Color("Primary")) .cornerRadius(3) .font(Font.system(size: 12, weight: .bold)) ・・・ } }
所感
- GoogleもAppleも丁寧なチュートリアルを用意しているので、ライトに試してみることができた
- どちらも思想や構成は似ており、AndroidViewとUIKitの時代より、両OSの開発を兼任する際のハードルは下がっていると感じた
- マテリアルコンポーネントの開発が別軸で進んでいるAndroidの方が、用意されたコンポーネントの種類は多そうに思った。
- これは単なる思想な違いでもある
- KMMで両方を試せると、比較しながら疑問が浮かぶので理解が進むので良き