Zenn
🤖

Skipでハマった問題まとめ

2025/03/18に公開
1

最近「Skip」というツールを使って、SwiftだけでiOSとAndroidの両方に対応するアプリ開発に挑戦しています(アプリは出来次第公開します)。

https://skip.tools/

Skipとは

SwiftでiOS / Androidアプリを開発できるツール・フレームワークです。
SwiftUIをJetpack Composeに変換したり、Pure SwiftコードをKotlinに変換(Transpiled Mode、SwiftのコードがKotlinのコードに翻訳される仕組み)したり、Pure SwiftコードをAndroid上でそのまま動かしたり(Native Mode)することができます。

今はNative Modeは使ってなくてTranspiled Modeで進めています(Kotlinコードが生成されるので問題の把握がしやすい)。

ハマったけどなんとかなった問題

単純にSkip側で対応してない機能や、Jetpack Compose側で対応してない機能が色々あります。
ただ、書き方を変えたりロジックを変えたりするとなんとかなることも色々あります。

Assetの色や画像リソースが簡単に扱えない問題

Assetのコンテンツを自動的に使えるようにしてくれる ImageResourceColorResource に未対応です。
なので例えば mainText みたいな色をxcassetsに追加した場合、 Color(.mainText) とは書けなくて、 Color("mainText", bundle: .module) と書く必要があります。

SwiftUIなら普通に使える系の機能も対応してなかったりすることもあります。
そういうのは一旦 #if !SKIP ... #endif で囲ったりして対処しています。
(ジェスチャー周りが全然動かないのでAndroidでは無視したりしています)

FirestoreのlimitでLong型が要求される問題

SkipFirebaseを使って firestorelimit を使おうとするとビルドが通りませんでした。
エラーを見ると Long 型を求められていました。
Swiftには Long はないよなと思って調べるとSwiftでは Int64 で対処可能でした。

https://github.com/skiptools/skip-firebase/blob/d1ce0b1edadb14ba8a772feafd82326ef5ea877b/Sources/SkipFirebaseFirestore/SkipFirebaseFirestore.swift#L380-L382

(最初からGitHubみとけば問題にならなかった問題)

ということでこんな感じのコードになりました。

let query = firestore.collection("collections")
  #if SKIP
    .limit(to: Int64(pageSize)) // SkipではLong型が必要なのでInt64で指定
  #else
    .limit(to: Int(pageSize)) // 通常のSwiftコードではInt型でOK
  #endif

Firestoreで where(in:) の引数型が合わない問題

同じくSkipFirebaseを使って firestorewhere(in:) を使いたかったので下記のようなコードを書いていました。

func search(_ values: [String]) async -> [Result] {
  Firestore.firestore().collection("collection")
    .where("key", in: values)
    ...
}

https://github.com/skiptools/skip-firebase/blob/d1ce0b1edadb14ba8a772feafd82326ef5ea877b/Sources/SkipFirebaseFirestore/SkipFirebaseFirestore.swift#L396-L398

ところがこれだとビルドが通りませんでした。
引数の型が [String] だったのが悪いのかKotlin側でビルドができなかったので下記のように修正しました。

func search(_ values: [any Sendable]) async -> [Result] {
  Firestore.firestore().collection("collection")
    .where("key", in: values)
    ...
}

[Any] だとSwift 6だとエラーが出ちゃうのでここでは any Sendable にすることで問題なくビルドすることができました。

iOSとAndroidで挙動が違う問題

SkipのSlackコミュニティがあるのでそこでも今聞いてるのですが、下記のようなコードだと、iOS / Androidで挙動が違ってしまいます。

struct WelcomeView : View {
    @State var heartBeating = false
    @Environment(ViewModel.self) var viewModel: ViewModel

    var body: some View {
        @Bindable var viewModel = viewModel
        ScrollView {
            VStack(spacing: 0) {
                Text("Hello [\(viewModel.name)](https://skip.tools)!")
                    .padding()
                Image(systemName: "heart.fill")
                    .foregroundStyle(.red)
                    .scaleEffect(heartBeating ? 1.5 : 1.0)
                    .animation(.easeInOut(duration: 1).repeatForever(), value: heartBeating)
                    .onAppear { heartBeating = true }

                LazyVGrid(columns: [GridItem(.adaptive(minimum: 80))]) {
                    ForEach(0..<100) { index in
                        ZStack {
                            Color.yellow
                            Text(String(describing: index))
                        }
                        .frame(height: 80)
                    }
                }
            }
            .font(.largeTitle)
        }
    }
}

screen

iOSでは画面全体がスクロールするのですが、Androidでは LazyVGrid の部分だけがスクロールしてしまい、画面全体がスクロールしません。

2025/03/19: 追記

SkipのSlackコミュニティにてこちらの解決方法を教えてもらいました。
ScrollView { LazyVGrid { Section { ... } header: { ... } } } のようにSection Headerを活用すればスクロールするとのことでした。

Skip Slack Community

修正後のコード

struct WelcomeView: View {
  @State var heartBeating = false
  @Environment(ViewModel.self) var viewModel: ViewModel

  var body: some View {
    @Bindable var viewModel = viewModel
    ScrollView {
      LazyVGrid(columns: [GridItem(.adaptive(minimum: 80))]) {
        Section {
          ForEach(0..<100) { index in
            ZStack {
              Color.yellow
              Text(String(describing: index))
            }
            .frame(height: 80)
          }
        } header: {
          VStack(spacing: 0) {
            Text("Hello [\(viewModel.name)](https://skip.tools)!")
              .padding()
            Image(systemName: "heart.fill")
              .foregroundStyle(.red)
              .scaleEffect(heartBeating ? 1.5 : 1.0)
              .animation(
                .easeInOut(duration: 1).repeatForever(), value: heartBeating
              )
              .onAppear { heartBeating = true }
          }
          .font(.largeTitle)
        }
      }
    }
  }
}

ハマって対処もできなかった問題

RemoteConfigが利用できない問題

これまたSkipFirebaseの問題なのですが、一応RemoteConfigも存在するので使えるんだろうとは思うのですが、使おうとすると全然ビルドが通らなくて一旦諦めました。
使える方法があれば知りたいです。

RemoteConfig Error 1

Remote Config Error 2

まとめ

この記事は若干ネガティブなことが多くて申し訳ないのですが、Swift・SwiftUIのコードだけでAndroidが動くというのはSwiftでコードを書きたいという人にとってはとても夢があるし個人的には可能性を感じています。
ドキュメントも割としっかりしているし、関連のコードもGitHubにあったりサンプルコードもあったりするのでiOSアプリの開発経験のある人なら開発はできるのではないかと思いました。

まだ課題もありますが、SwiftのコードだけでAndroidアプリを開発できるSkipは、Swiftエンジニアにとって非常に魅力的なツールです。個人的にも開発を楽しみながら、Skipコミュニティとともに成長を見守っていきたいです。Skipの話ができる仲間が増えると嬉しいですね!

https://skip.tools/docs
https://github.com/skiptools/skip

(一部ChatGPTに表現を直してもらいましたが90%ぐらいは自分で書きました)

1

Discussion

ログインするとコメントできます