所感箱

所感を書きためていきます

マルチプラットフォーム構成で、SwiftUIとJetpack Composeを試す

モチベーション

  • DroidKaigi2023で、JetpackCompoeに触れたことで、SwiftUIにも触れたくなった
  • マルチプラットフォーム構成で、それぞれの特徴を比較しながら試せると理解が深まりそう

何を試すか

ステップ

詳しくはこちらを参照:https://kotlinlang.org/docs/multiplatform-mobile-getting-started.html

  1. Android Studioで新規プロジェクト -> Kotlin Multiplatformを選択
  2. プロジェクト構成の設定画面。 iOS用にCocoaPodsの導入可能なようだ。
  3. 添付画像のような構成でプロジェクトが作成される
  4. iOSAndroidで、各種コンポーネントのカタログページを作る

ちなみに今回の記載の内容は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を指定するには、AndroidModifier.clipを使用し、iOSではView.clipShapeを利用する。

デフォルトで、いくつかのクラスが用意されていて抜粋すると以下の通り。

JetpackCompose

  • RoundedCornerShape
    • 円形のCircleShapeの実態はRoundedCornerShape(50)
  • RectangleShape
  • CutCornerShape

SwiftUI

  • Circle
  • RoundedRectangle
  • Rectangle
  • Capsule

テキスト

テキストに文字スタイルを設定するのは、AndroidTextViewの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ではLazyVStackListを使う設計になっているようだった。 今回は、簡素なページなので、JetpackComposeではColumnRow、SwiftUIではVStackHStackを利用した。

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))

       ・・・
    }
}

所感

  • GoogleAppleも丁寧なチュートリアルを用意しているので、ライトに試してみることができた
  • どちらも思想や構成は似ており、AndroidViewとUIKitの時代より、両OSの開発を兼任する際のハードルは下がっていると感じた
  • マテリアルコンポーネントの開発が別軸で進んでいるAndroidの方が、用意されたコンポーネントの種類は多そうに思った。
    • これは単なる思想な違いでもある
  • KMMで両方を試せると、比較しながら疑問が浮かぶので理解が進むので良き

Appendix

developer.apple.com

developer.android.com