Capacitor

複数のアプリを作成管理する方法

完全に独立した複数アプリを管理する場合、「pnpm workspaces」や「npm workspaces」を活用したモノレポ構成が最もスッキリまとまり、依存関係の競合も防げるため最適です。

以下に、最も軽量で扱いやすい npm workspaces を使った具体的なディレクトリ構成と設定手順を解説します。


1. 推奨のディレクトリ構成

ルート(親)配下に、共通で使う共有ライブラリ(shared)と、完全に独立したCapacitorアプリ(app-a, app-b)を配置します。

my-capacitor-monorepo/
├── package.json             # ルートの設定ファイル
├── node_modules/            # 依存パッケージ(一元管理)
└── apps/
    ├── app-a/               # アプリA(独立したCapacitor環境)
    │   ├── package.json
    │   ├── capacitor.config.ts
    │   ├── android/        # アプリA専用のネイティブプロジェクト
    │   └── ios/
    └── app-b/               # アプリB(独立したCapacitor環境)
        ├── package.json
        ├── capacitor.config.ts
        ├── android/        # アプリB専用のネイティブプロジェクト
        └── ios/

フォルダを上記のように配置し、package.jsonを隣に置いてpackage.jsonは下記のようにすればOK


2. ルートの package.json の設定

プロジェクトのルート直下にある package.json で、apps ディレクトリ配下をワークスペースとして指定します。

{
  "name": "my-capacitor-monorepo",
  "private": true,
  "workspaces": [
    "apps/*"
  ],
  "scripts": {
    "build:a": "npm run build --workspace=apps/app-a",
    "sync:a": "npm run cap:sync --workspace=apps/app-a",
    "build:b": "npm run build --workspace=apps/app-b",
    "sync:b": "npm run cap:sync --workspace=apps/app-b"
  }
}

3. 各アプリ(子プロジェクト)の設定

それぞれのアプリ(例: apps/app-a)の内部は、完全に独立したCapacitorアプリとして動作させます。

① apps/app-a/package.json の例

バージョン確認方法
npx cap --version

{
  "name": "app-a",
  "version": "1.0.0",
  "scripts": {
    "build": "vite build",
    "cap:sync": "cap sync"
  },
  "dependencies": {
    "@capacitor/core": "^6.0.0"
  },
  "devDependencies": {
    "@capacitor/cli": "^6.0.0"
  }
}

② apps/app-a/capacitor.config.ts の例

各アプリの個別設定(App IDやWeb資産の出力先)を記述します。

import { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.example.appa',
  appName: 'App A',
  webDir: 'dist', // 各アプリ内のビルド成果物フォルダを指定
  bundledWebRuntime: false
};

export default config;

4. 運用・開発コマンド

モノレポ構成では、すべてのパッケージインストールやビルドをルートから制御できます。

  • 全体の依存パッケージをインストールする(ルートで実行)npm install ※依存関係はルートの node_modules に集約されるため、ディスク容量を節約できます。
  • 特定のアプリ(App A)だけをビルド・同期するnpm run build:a npm run sync:a
  • 特定のアプリのネイティブIDE(Android Studio / Xcode)を開く
    各アプリのディレクトリに移動してコマンドを実行します。cd apps/app-a npx cap open android

5. 注意すべきポイント

  • Capacitor CLIの実行npx cap sync などのコマンドは、必ず対象アプリのディレクトリ(apps/app-a など)に移動してから実行するか、上記のように workspace スクリプト経由で実行してください。ルートで直接叩くとエラーになります。
  • プラグインの競合: ネイティブプラグイン(Push通知やカメラなど)は各アプリの package.json に個別にインストールしますが、モノレポの仕組み上、一部のプラグインがAndroid/iOSの自動リンクで干渉することがあります。その場合は、各アプリフォルダに独立した node_modules を強制生成する設定(pnpmの shamefully-hoist など)への移行を検討してください。

“cap:sync”: “cap sync”
この記述があるとどうなりますか?

この記述があると、「各アプリのディレクトリに移動した状態で、npm run cap:sync という短いコマンドを入力するだけで、Capacitorの同期処理が実行できる」ようになります。

具体的な仕組みとメリットは以下の3点です。

1. npx を省略して実行できる

本来、Capacitorのコマンドを実行するには npx cap sync と入力する必要があります。
この記述を package.json の scripts に登録しておくことで、npm run cap:sync で呼び出せるようになります。

2. ルート(親)から子アプリを操作しやすくなる(モノレポの最大のメリット)

モノレポ構成(npm workspaces)では、ルートディレクトリにいながら、特定の子アプリのスクリプトを呼び出す機能があります。
子アプリ(app-a)の package.json にこの記述があることで、ルートディレクトリから一歩も移動せずに以下のコマンドで同期が可能になります。

# ルート(親)にいる状態で実行
npm run cap:sync --workspace=apps/app-a

もし子アプリ側にこの記述がないと、ルートから直接Capacitorを同期させることが難しくなり、毎回 cd apps/app-a で移動してから npx cap sync を叩く手間が発生します。

3. 他のビルドコマンドと連携(自動化)しやすくなる

アプリのWebコード(ViteやReactなど)をビルドした後に、自動でCapacitorへ同期させたい場合、コマンドを連続して実行(チェーン)できるようになります。

例えば、子アプリの package.json を以下のように書き換えることで、「Webのビルドとネイティブへの同期」を1つのコマンドにまとめることも可能になります。

"scripts": {
  "build": "vite build",
  "cap:sync": "cap sync",
  "deploy": "npm run build && npm run cap:sync" 
}

※ npm run deploy を実行するだけで、ビルドからCapacitorの同期までが自動で一気に完了します。


この設定は、これからの複数アプリ開発の効率を大きく左右する便利な「ショートカット登録」のようなものです。

もしよろしければ、現在開発を想定しているフロントエンドのフレームワーク(Next.js、Vite+React、Ionicなど)を教えていただけますか?それに応じた最適なスクリプトの組み合わせをご提案いたします。

iOS版を作成する

URLを指定してゲーム化しているページをそのままアプリにする(ラップする)。(存在しないページを指定するとただの白いページになる)

環境準備(Mac必須)

必要なもの:

  • Node.js
  • Xcode

プロジェクト作成

ターミナルで👇

設定を聞かれるので:

  • App name → ゲーム名
  • App ID → com.example.game(適当でOK)

Webゲームを組み込む

方法

WordPressのURLをそのまま使う👇

👉 これで「WebViewでサイト表示」になる

“webDir”: “public”, のカンマの追加を忘れないこと

Xcode側の設定(重要)

やること👇

① Team設定

  • Signing & Capabilities
  • Apple ID選択

② アイコン設定

  • AppIconに画像入れる

③ フルスクリーン設定

Safariっぽさ消す👇

④ HTTP対策(必要なら)

httpsじゃない場合:

※本番はHTTPS必須

注意点

❌ iOS審査落ち

Appleはこれ見ます👇

  • 「ただのWebViewアプリ」NGになりやすい
  • 最低限:

👉 例:

  • ローディング画面追加
  • オフライン時の表示
  • メニューUI改善

ファイルからビルドする

やりたいこと

👉 Webゲームをそのままローカルに置いてビルド

つまり👇

  • ❌ URL読み込み(server.url)
  • ✅ HTML/JS/CSSをアプリに同梱

UIやゲーム本体は自分で書いてAPIだけWPのものを使う感じ

コマンド

  1. Webビルドnpm run build などで最新のWebファイルを生成。
  2. 同期npx cap sync を実行し、Web資産をネイティブ側に反映。
  3. 実行npx cap open android または npx cap open ios で Android Studio や Xcode を開き、そこから実機やエミュレータで起動します。

■ 全体の完成イメージ

① server設定を消す(重要)

👉 これ削除

② webDirを確認

👉 publicフォルダがルートになる

③ ファイル配置

最重要👇

👉 必ず index.html を置く

例:

注意点

index.html修正(ここ超重要)

❌ ダメ(よくあるミス)

👉 / から始まると動かない

Capacitor同期

■ よくあるハマりポイント⚠️

① 画面真っ白

原因ほぼこれ👇

  • パスミス
  • JS読み込めてない

👉 Xcodeのコンソールでエラー確認

② WordPressのまま使ってる

👉 PHPは動かない

対策👇

  • APIとして使う(fetch)
  • または静的化する

③ 画像が出ない

👉 パス or 大文字小文字ミス

④ fetch先がブロックされる

👉 CORS問題

Capacitor準備が終わってXcode起動までメモ

名前にハイフンやアンダーバーを入れない方が良い、IDにアンダーバーとか入れるとエラーキャメルとかが安定か?

# 初期化(アプリ名とApp IDを設定)
npx cap init

[?] What is the web asset directory for your app?
    This directory should contain the final index.html of your app.
にはフォルダのパスを入れる

npm install @capacitor/ios
npx cap add ios
npx cap open ios

出来上がったら次からはターミナルで該当アプリのiOSフォルダがあるところに移動し【npx cap open ios】を打ち込めばOK
同期コマンド【npx cap sync】

コードの編集

Capacitorの性質が元フォルダを同期させてIDEに送るので元ファイルを編集する必要がある。(npx cap syncすると元ファイルの状態にリセットされる)
そのためWebで使っているフォルダがある場合、別途コピーして置く必要がある。
IDE側で作成されるファイル(Swiftファイルとか)はそちらで編集してもリセットされない。

CapacitorとSwiftの連携方法

以下はCapacitorのバージョンやプロジェクト構成に寄り異なるようです。

Capacitorでは、

JavaScript ⇄ Capacitor Plugin / Bridge ⇄ Swift

という橋を作って通信します。

また、重要なのは、Capacitorで表示しているHTML/JavaScript部分は基本的にSwiftUIのViewではありません。
Xcode側にはWKWebViewがあり、そこにHTML/JSが表示されています。


全体像

現在の構成は、おそらくこんな感じです。

HTML
 ├─ index.html
 ├─ game.js
 └─ style.css
        ↓
   Capacitor
        ↓
      WKWebView
        ↓
       iOS App
        ↓
      Xcode / Swift

ここにSwift側との通信を追加すると、

JavaScript
    ↓
Capacitor Bridge
    ↓
Swift Plugin
    ↓
Swiftの処理
    ↓
結果をJavaScriptへ返す

という構造になります。


SwiftUIはどこに入るのか?

ここが一番重要です。

例えば、

  • HTMLゲームを表示する
  • JavaScriptからiOSの機能を呼ぶ
  • JavaScriptからSwiftの処理を呼ぶ

だけなら、SwiftUIは必須ではありません。

SwiftUIは、

ContentView.swift

などで

var body: some View {
    ...
}

とUIを作るためのものです。

一方、CapacitorのJavaScriptと通信する部分は、

SwiftUI Viewではなく、普通のSwiftコード / Capacitor Plugin

として作るのが基本です。


例えばこういうことができます

例えばHTML側に

console.log("iOSの処理を呼びたい");

があるとして、

JavaScriptから

NativeAPI.echo({
    message: "こんにちは"
});

のようにSwiftを呼びます。

するとSwift側で、

func echo(...)

を実行して、

こんにちは

をJavaScriptへ返せます。

つまり、

JavaScript
   ↓
「Swiftさん、この処理して」
   ↓
Swift
   ↓
「結果はこれです」
   ↓
JavaScript

という通信です。


Capacitor Pluginを作る

例えば、

ios/

側にSwiftのPluginを作ります。

概念的にはこうです。

import Foundation
import Capacitor

@objc(MyNativePlugin)
public class MyNativePlugin: CAPPlugin {

    @objc func echo(_ call: CAPPluginCall) {

        let message = call.getString("message") ?? ""

        print("Swift側で受信: \(message)")

        call.resolve([
            "message": message
        ])
    }
}

これがSwift側です。


JavaScript側から呼ぶ

JavaScriptでは、

import { registerPlugin } from '@capacitor/core';

const MyNativePlugin = registerPlugin('MyNativePlugin');

async function testNative() {
    const result = await MyNativePlugin.echo({
        message: 'JavaScriptからこんにちは'
    });

    console.log(result.message);
}

という感じになります。

すると、

JavaScript
    ↓
MyNativePlugin.echo()
    ↓
Swiftのecho()
    ↓
call.resolve()
    ↓
JavaScript

という流れになります。


ただし、実際にはもう少し重要なポイントがあります

Capacitorには大きく2種類の考え方があります。

① 既存のCapacitor Pluginを使う

例えば、

Camera
Filesystem
Preferences
Device
Network
App
Browser
Haptics

など。

この場合はSwiftを書く必要がほとんどありません。

JavaScriptから、

import { Device } from '@capacitor/device';

const info = await Device.getInfo();

console.log(info.platform);

のように使えます。


② 自分専用のSwift処理を作る

例えばあなたのゲームで、

JavaScript
 ↓
Swift
 ↓
UserDefaults

とか、

JavaScript
 ↓
Swift
 ↓
Keychain

とか、

JavaScript
 ↓
Swift
 ↓
StoreKit

とか、

JavaScript
 ↓
Swift
 ↓
AdMob

などをしたい場合です。

この場合は独自Capacitor Pluginを作ります。


SwiftUIを使いたい場合

ここは少し話が変わります。

例えば、

HTMLゲーム

JavaScript

でゲームを表示している。

そこから、

「設定」

を押したら、

SwiftUIで作った設定画面

を表示したい。

これは可能です。

構造としては、

HTML / JavaScript
        ↓
Capacitor Plugin
        ↓
Swift
        ↓
SwiftUI View

となります。

つまりSwiftUIを使うこと自体はできます。

ただし、

JavaScriptの中にSwiftUIを書くことはありません。


例えば

JavaScriptから

await NativeUI.openSettings();

と呼ぶ。

Swift側で、

@objc func openSettings(_ call: CAPPluginCall) {

    // SwiftUI画面を表示
}

として、

struct SettingsView: View {
    var body: some View {
        VStack {
            Text("設定")
        }
    }
}

を表示する、という構成です。


つまり、ファイルの役割はこう考えると分かりやすいです

現在のあなたのプロジェクトなら、

www/
 ├─ index.html
 ├─ game.js
 └─ style.css

ここは、

Web側

です。

そして、

ios/
 └─ App/
     ├─ AppDelegate.swift
     ├─ ...
     └─ MyNativePlugin.swift

などが、

iOSネイティブ側

です。

さらに必要なら、

SettingsView.swift

のようなSwiftUI Viewを追加します。


一番おすすめの構成

あなたのように、

HTML + JavaScriptで既にゲームが完成していて、CapacitorでiOSアプリ化している

なら、最初はこの構成が非常に分かりやすいです。

【Web】
HTML
CSS
JavaScript
   │
   │ Capacitor
   ↓
【Native】
MyPlugin.swift
   │
   ├─ iOS API
   ├─ StoreKit
   ├─ UserDefaults
   ├─ Keychain
   └─ その他

SwiftUIが必要になった場合だけ、

MyPlugin.swift
       ↓
SettingsView.swift

という形でSwiftUIを追加します。


例えば「JavaScriptからSwiftを呼びたい」だけなら

実際のコードは、

game.js
     ↓
registerPlugin()
     ↓
MyPlugin.swift
     ↓
call.resolve()
     ↓
game.js

という形になります。


そして逆方向も可能です

SwiftからJavaScriptへ通知することもできます。

例えばSwift側で、

「購入が完了した」

と分かったら、

Swift
 ↓
JavaScriptへイベント送信
 ↓
ゲーム側のUI更新

ということも可能です。

これがCapacitorのかなり重要な部分です。


今のあなたのケースなら

以前の構成を見る限り、あなたは

HTML + JavaScript + CSSのゲームをCapacitorでラップして、XcodeでiOSアプリとして動かしている

状態なので、次に覚えるべきなのは、

JavaScript
   ↕
Capacitor Plugin
   ↕
Swift

です。

SwiftUIをJavaScriptに直接入れるのではありません。

そして、SwiftUIは必要になったときに、

JavaScript
 ↓
Capacitor Plugin
 ↓
Swift
 ↓
SwiftUI

バージョン8.3.1での連携方法

質問です。
Swift側
@objc(MyNativePlugin)
public class MyNativePlugin: CAPPlugin {}
のMyNativePluginは合わせる必要がありますか?
もし合わせる必要がない場合はどっちの記述名がJS側、プラグイン登録等で必要になりますか?

はい、合わせる必要があります。ただし、Capacitorでは「名前が3か所ある」ので、そこを分けて考えると分かりやすいです。

Capacitor 8.5.0 では、今回の例なら基本的に次の名前を同じにしておくのが一番安全です。

@objc(MyNativePlugin)
public class MyNativePlugin: CAPPlugin {
    ...
}

ここで出てくる MyNativePlugin には、実は役割が違うものがあります。

① Swiftのクラス名

public class MyNativePlugin: CAPPlugin

これはSwiftのクラス名です。

たとえば、

public class WordPressPlugin: CAPPlugin

ならSwift側のクラス名は WordPressPlugin です。


② @objc(...) の名前

@objc(MyNativePlugin)

こちらはObjective-Cランタイム上での名前です。

CapacitorのPluginとして公開する際に使われます。

したがって、通常は、

@objc(MyNativePlugin)
public class MyNativePlugin: CAPPlugin

のように同じ名前にします

別名にすることも技術的には可能ですが、混乱するだけなので、最初は同じ名前にするのがおすすめです。

例えば、

@objc(MyNativePlugin)
public class MyNativePlugin: CAPPlugin

なら、

Swiftクラス名     MyNativePlugin
Objective-C名      MyNativePlugin

です。


③ JavaScript側のPlugin名

ここが一番重要です。

JavaScriptで、

const MyNativePlugin = registerPlugin('MyNativePlugin');

と書いた場合、

'MyNativePlugin'

Pluginの名前です。

この名前がネイティブ側のPluginとして認識される名前になります。

そのため、今回の最小構成なら、

Swift
  @objc(MyNativePlugin)
  class MyNativePlugin

        ↓

JavaScript
  registerPlugin('MyNativePlugin')

と揃える、と考えると分かりやすいです。


ただし、JavaScriptの変数名は別物です

ここは非常に重要です。

例えばJavaScriptで、

const NativeAPI = registerPlugin('MyNativePlugin');

と書いても構いません。

この場合、

NativeAPI

は単なるJavaScript側の変数名です。

一方、

registerPlugin('MyNativePlugin')

MyNativePlugin

がPlugin名です。

つまり、

const NativeAPI = registerPlugin('MyNativePlugin');

は、

JavaScript変数名   → NativeAPI
Plugin名            → MyNativePlugin

です。

なので、こんなこともできます。

const hoge = registerPlugin('MyNativePlugin');

そして、

const result = await hoge.hello({
    message: 'こんにちは'
});

でも動く考え方です。

ただし、非常に分かりにくいので、

const MyNativePlugin = registerPlugin('MyNativePlugin');

とするのが自然です。


さらに「メソッド名」も別です

Swift側に、

@objc func hello(_ call: CAPPluginCall)

と書いた場合、

hello

がJavaScriptから呼ぶメソッド名になります。

JavaScript側は、

await MyNativePlugin.hello(...)

です。

したがって全体では、

Swift
────────────────────

@objc(MyNativePlugin)
public class MyNativePlugin: CAPPlugin {

    @objc func hello(_ call: CAPPluginCall) {
        ...
    }
}

JavaScript
────────────────────

const MyNativePlugin =
    registerPlugin('MyNativePlugin');

await MyNativePlugin.hello({
    message: 'こんにちは'
});

という関係です。


4つに分解すると分かりやすいです

記述役割今回
class MyNativePluginSwiftクラス名MyNativePlugin
@objc(MyNativePlugin)Objective-C上の名前MyNativePlugin
registerPlugin('MyNativePlugin')CapacitorのPlugin名MyNativePlugin
MyNativePlugin.hello()JS側から呼ぶメソッドhello

そして、

const MyNativePlugin = ...

の左側の MyNativePlugin はJavaScriptの変数名なので、実は自由です。


なので今回なら、この形にします

Swift

import Foundation
import Capacitor

@objc(MyNativePlugin)
public class MyNativePlugin: CAPPlugin {

    @objc func hello(_ call: CAPPluginCall) {

        let message = call.getString("message") ?? ""

        print("Swift側で受信: \(message)")

        call.resolve([
            "message": "Swiftから返事です。受信内容: \(message)"
        ])
    }
}

JavaScript

import { registerPlugin } from '@capacitor/core';

const MyNativePlugin = registerPlugin('MyNativePlugin');

async function testSwift() {
    try {
        const result = await MyNativePlugin.hello({
            message: 'JavaScriptからこんにちは!'
        });

        console.log('Swiftからの返事:', result.message);

    } catch (error) {
        console.error('Swift呼び出しエラー:', error);
    }
}

そして今回のCapacitor 8.5.0について

今回、

Capacitor CLI 8.3.1
@capacitor/core 8.5.0
@capacitor/ios 8.5.0

という構成ですね。

ここで一点訂正があります。先ほど私が説明した「AppDelegate.swift に登録する」という部分は、Capacitor 8では、そのまま採用する前提で進めない方がいいです。Capacitor 8のPlugin登録方法に合わせて構築する必要があります。

今回のように独自Swift Pluginを作る場合は、Pluginの登録部分まで正しく設定しないと、JavaScriptから registerPlugin() してもSwiftへ到達しません。

なので次は、

MyNativePlugin.swift
+
Plugin登録
+
game.js

Capacitor 8.5.0に合わせた正式な構成で作るのがよいです。

Capacitor 8公式ドキュメントでは、アプリ内のカスタムSwiftコードは CAPBridgedPlugin を実装したSwiftクラスを作り、CAPBridgeViewController の capacitorDidLoad() で registerPluginInstance() する方式が案内されています。JavaScript側の registerPlugin() に渡す名前はSwift側の jsName と一致させます。

今回作る最終形はこれです。

JavaScript
   │
   │ Echo.echo(...)
   ▼
Capacitor Bridge
   │
   ▼
EchoPlugin.swift
   │
   │ Swiftで処理
   ▼
JavaScriptへ結果を返す

1. Xcodeで EchoPlugin.swift を作る

Xcode左側のプロジェクトナビゲータで、

App

を右クリックします。

New File...

Swift File

→ ファイル名を

EchoPlugin.swift

にします。

公式ドキュメントでもこの方法で EchoPlugin.swift を作る手順になっています。

中身を全部これにします。

ここで名前を整理すると

@objc(EchoPlugin)
public class EchoPlugin

はSwift側のクラスです。

public let identifier = "EchoPlugin"

はPluginの識別子。

public let jsName = "Echo"

JavaScriptから登録するときの名前です。

そして、

CAPPluginMethod(name: "echo", ...)

の echo がJavaScriptから呼ぶメソッド名です。

つまり今回の対応関係は、

Swiftクラス
EchoPlugin

identifier
EchoPlugin

JavaScript用Plugin名
Echo

JavaScriptから呼ぶメソッド
echo

です。

公式例もこの構成になっています。


2. なぜ CAPBridgedPlugin が必要なのか

ここは先ほどの回答からの重要な修正点です。

以前の説明では、

public class MyNativePlugin: CAPPlugin

だけを例にしていました。

Capacitor 8では今回のカスタムPluginの場合、これではなく

public class EchoPlugin: CAPPlugin, CAPBridgedPlugin

とします。

そして、

identifier
jsName
pluginMethods

を定義します。

これが現在の公式の方式です。(Capacitor)

また、@objc はCapacitorの動的なPlugin呼び出しに必要です。(Capacitor)


3. 次にPlugin登録用のViewControllerを作る

ここが今回の一番重要なところです。

Xcodeで再び、

App

を右クリック。

New File...

Swift File

ファイル名:

MyViewController.swift

にします。

中身:

これで、

CapacitorのBridgeがロードされた
        ↓
EchoPluginを登録

という処理になります。

公式ドキュメントでも、

override open func capacitorDidLoad() {
    bridge?.registerPluginInstance(EchoPlugin())
}

という方式が指定されています。


4. ここで非常に重要

MyViewController.swift を作っただけではダメです。

現在のアプリが使用している

CAPBridgeViewController

を、

MyViewController

に変更する必要があります。

ここはプロジェクトの作り方によって設定箇所が違います。

Capacitorアプリでは、通常Storyboard側でBridge View Controllerを指定しています。

Xcode左側で、

App

の中にある

Main.storyboard

またはStoryboard相当のUIファイルを探してください。

開くと、

CAPBridgeViewController

が配置されているはずです。

そこを選択して、右側の

Identity Inspector

を開きます。

プルダウン選択ではなく直接打ち込む

Custom Class のところを、

CAPBridgeViewController

から

MyViewController

に変更します。

イメージとして、

Before

CAPBridgeViewController
        ↑
Main.storyboard


After

MyViewController
        ↑
Main.storyboard
        ↑
CAPBridgeViewControllerを継承

です。


5. JavaScript側を作る

HTML側のJavaScriptの読み込み方による

ここからWeb側です。

あなたの現在の構成が、

www/
  index.html
  game.js
  style.css

なら、まず game.js に以下を追加します。

import { registerPlugin } from '@capacitor/core';

const Echo = registerPlugin('Echo');

async function testSwift() {

    try {

        console.log('JavaScript → Swift 開始');

        const result = await Echo.echo({
            value: 'JavaScriptからこんにちは!'
        });

        console.log('Swift → JavaScript 成功');
        console.log('Swiftからの返事:', result.value);

    } catch (error) {

        console.error('Swift呼び出しエラー:', error);

    }
}

公式ドキュメントでも、

const Echo = registerPlugin('Echo');

としており、ここで使う "Echo" はSwift側の

public let jsName = "Echo"

と一致させる必要があります。


6. HTMLからテストする

index.html に、

<button onclick="testSwift()">
    Swiftを呼ぶ
</button>

を追加します。

ただし、ここで一つ注意があります。

あなたの既存ゲームが普通の、

<script src="game.js"></script>

でJavaScriptを読み込んでいる場合、

import { registerPlugin } from '@capacitor/core';

を使うにはES Moduleとして読み込む必要があります。

つまり、

<script type="module" src="game.js"></script>

に変更する必要があります。

ただ、ここはあなたの既存ゲームのJavaScript構成を崩す可能性があります

なので、実際の index.html を見てから変更するのが安全です。


7. JavaScript → Swift → JavaScript

ここまでできれば、流れはこうなります。

ボタンを押す。

testSwift()

Echo.echo({
    value: 'JavaScriptからこんにちは!'
})

Capacitor Bridge

Swift:

@objc func echo(_ call: CAPPluginCall)

let value = call.getString("value")

Swiftで、

call.resolve([
    "value": "Swiftから返事: \(value)"
])

JavaScriptに戻る。

result.value

Swiftから返事: JavaScriptからこんにちは!

となります。

これはまさに、

JavaScript
    ↓
Swift
    ↓
JavaScript

です。


8. Xcodeで確認する

Xcodeから実機またはSimulatorで実行します。

その後、SafariのWeb InspectorなどでWebViewのコンソールを見ると、

JavaScript → Swift 開始
Swift → JavaScript 成功
Swiftからの返事: Swiftから返事: JavaScriptからこんにちは!

となるはずです。

さらにXcode側のコンソールには、

========== EchoPlugin ==========
JavaScriptから受信: JavaScriptからこんにちは!
================================

が出ます。

これなら完全に通信できています。


9. ファイル構成は最終的にこうなります

あなたのプロジェクト
│
├─ www/
│  ├─ index.html
│  ├─ game.js
│  └─ style.css
│
├─ capacitor.config.json
│
└─ ios/
   └─ App/
      │
      ├─ AppDelegate.swift
      │
      ├─ EchoPlugin.swift       ← 今回追加
      │
      ├─ MyViewController.swift ← 今回追加
      │
      └─ Main.storyboard        ← MyViewControllerを指定

この構造です。


10. 名前の関係をもう一度

今回、非常に重要なのでここだけ整理します。

Swift

@objc(EchoPlugin)
public class EchoPlugin: CAPPlugin, CAPBridgedPlugin {

Swift Plugin名

public let jsName = "Echo"

JavaScript

const Echo = registerPlugin('Echo');

呼び出し

Echo.echo(...)

つまり、

@objc(EchoPlugin)
      ↓
Swiftクラス

jsName = "Echo"
      ↓
JavaScript Plugin名

registerPlugin("Echo")
      ↓
JavaScript側の接続

Echo.echo()
      ↓
Swiftの echo()

です。


11. identifier と jsName は同じでなくていい

ここも前の質問につながります。

例えば、

public let identifier = "MyAwesomeNativePlugin"
public let jsName = "Echo"

でも構いません。

この場合、

registerPlugin('Echo')

です。

JavaScriptが合わせるべきなのは、

identifier ではなく jsName

です。

Capacitor公式の例でも、

identifier = "EchoPlugin"
jsName = "Echo"

と別名になっています。

これは前の質問への正確な回答にもなります。


12. 最初はこの名前で固定するのがおすすめ

今回は混乱を避けるため、

Swift class       EchoPlugin
identifier        EchoPlugin
jsName            Echo
JS variable       Echo
method            echo

で進めるのがよいです。


HTMLのJSの読み込み方が<script src="ふぁいるめい.js"></script>の場合のJSコード(import不要)

APIの取得コードの例

元コード

修正後

画像ダウンロードのコード例、画像がpublic内にある場合

iOSの共有にダウンロード

FilesystemとShareを追加

プロジェクトのルートで、

npm install @capacitor/filesystem @capacitor/sharenpx cap sync ios

を実行します。

iOSの写真にダウンロード

Media Pluginを追加

プロジェクトのルートで実行してください。

npm install @capacitor-community/medianpx cap sync ios

このPluginはiOS/Androidの写真・動画保存用です。savePhoto() がカメラロール保存を担当します。

iOSの写真アクセス許可を追加

Xcodeで、

App└─ Info

または Info.plist を開きます。

Privacy - Photo Library Usage Description

を追加して、例えば、

写真を保存するために写真ライブラリへのアクセスを使用します。

としてください。

このPluginのiOS設定では NSPhotoLibraryUsageDescription が必要です。

コード

inputにフォーカスするとズームされる問題

iOSのSafari / WKWebViewで、入力欄の文字サイズが小さいと自動的にページをズームする挙動によるものです。

今回の

<input type="text" id="display-name" maxlength="10" placeholder="名前(10文字以内)">

なら、まずCSSで font-size: 16px 以上にするのが一番簡単で確実です。

#display-name {
    font-size: 16px;
}

たとえば既存のスタイルに、

#display-name {
    width: 100%;
    box-sizing: border-box;
    font-size: 16px;
}

と追加してください。

なぜ16pxなのか

iOSのWebViewでは、input の文字サイズが16px未満だと、入力時に「文字を見やすくするため」と考えられる自動ズームが発生することがあります。

そのため、

font-size: 14px;

などになっていた場合、

font-size: 16px;

に変更するだけで、今回のズーム問題が解消する可能性が高いです。


もし 16px にしてもズームする場合

HTMLの <head> に viewport 設定があるか確認してください。

<meta name="viewport"
      content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

特に、

maximum-scale=1.0
user-scalable=no

を追加すると、ユーザーによるピンチズームも無効化できます。

ただし、アクセシビリティの観点ではユーザーのズーム操作を完全に禁止する方法はあまり推奨されません

今回のような「入力欄をタップした瞬間だけ勝手にズームされる」という問題なら、まずは

#display-name {
    font-size: 16px;
}

を試すのがおすすめです。


もう一つ注意点があります

今回のゲームはCapacitorでiOSアプリ化しているので、CSS側だけではなく、キーボード表示時のWebViewのリサイズによって「画面全体がズレたように見える」場合もあります。

つまり、

① 入力欄フォーカス → 自動ズーム

② キーボード表示 → WebViewの表示領域が変化

は別問題です。

Windows導入方法

Windows 11環境に Android Studio と Capacitor を導入し、Androidアプリの開発環境を構築する手順です。

1. 前提条件のインストール

Capacitorの動作には Node.js が必須です。

  • Node.jsの導入: Node.js 公式サイト から LTS(推奨版) をダウンロードしてインストールします。
  • 確認方法: コマンドプロンプトを開き、以下を実行してバージョンが表示されればOKです。
node -v
npm -v

2. Android Studio のインストールと設定

ダウンロード: Android Studio 公式サイト から Windows 用インストーラーをダウンロードします。

セットアップ: インストーラーを起動し、すべてデフォルト設定のまま進めて完了させます。

SDKの準備: 初回起動時のセットアップウィザードに従い、Android SDK や Android Virtual Device(エミュレータ) をインストールします。

環境変数の設定 (重要):

Windowsのスタートメニューで「環境変数」を検索し、「システム環境変数の編集」を開きます。

「環境変数」ボタンをクリックします。

「ユーザー環境変数」の「新規」から以下を追加します。

変数名: ANDROID_HOME

変数値: C:\Users\ユーザー名\AppData\Local\Android\Sdk(※ユーザー名はご自身のWindowsアカウント名)

「ユーザー環境変数」または「システム環境変数」の Path を選択して「編集」をクリックし、末尾に以下を追記します。

C:\Users\ユーザー名\AppData\Local\Android\Sdk\platform-tools

3. Capacitor のプロジェクトへの導入

既存、または新規のWebアプリプロジェクト(React, Vue, Angular, Vanilla JSなど)にCapacitorを組み込みます。

プロジェクトの作成・移動:

コマンドプロンプトでプロジェクトのルートフォルダに移動します。

Capacitorのインストール:

npm install @capacitor/core @capacitor/cli

Capacitorの初期化:

  • アプリ名とパッケージID(例: com.example.myapp)を設定します。
npx cap init

Androidプラットフォームの追加:

npm install @capacitor/android
npx cap add android

4. ビルドと実行

Webアプリのビルド:

  • お使いのフレームワークに応じて、Web資産(dist や build フォルダ)を出力します。
npm run build

Capacitorとの同期:

  • Webアプリの成果物をAndroidプロジェクトにコピーします。
npx cap sync

Android Studio で開く / 実行:

  • 以下のコマンドを実行すると、自動的にAndroid Studioが起動してプロジェクトが読み込まれます。
npx cap open android

Android Studioの画面右上にある 再生ボタン(Runボタン) を押すと、エミュレータまたは実機でアプリが起動します

タイトルとURLをコピーしました