第拾玖話
SwiftUI・Expo との比較
Asuka にあるもの、まだないもの
各行は、アプリでよく必要になる機能です。SwiftUI と Expo がそれをどう実現しているか、Asuka には何があるかを並べています。行に特に記載がない限り、✓ は Asuka が動くすべてのプラットフォーム(iOS、Android、Mac、Windows、Linux、Web)で使えることを表します。あるプラットフォームに機能がない場合(Mac にはセンサーがなく、ブラウザーにはホーム画面ウィジェットがありません)、そのパッケージは「対応していない」と答えます。スマートフォンにできてブラウザーにはできないことについて、ブラウザーで各パッケージが代わりに何をするかは Web を参照してください。
SwiftUI は Mac でも動きます。Expo は iOS、Android、Web で動きます。React Native は、Expo の外にある Microsoft の React Native for Windows and macOS によって Windows と Mac にも対応しています。デスクトップのフレームワーク(Electron、Tauri、Wails)と Asuka の比較はデスクトップのフレームワークを、デスクトップアプリがウィンドウ以外に必要とするものについてはデスクトップアプリの機能を参照してください。UI を自前で描画する MyGo との比較は Asuka と MyGo を参照してください。
✓ 対応 · ◐ 一部対応(足りないものを記載) · ✗ 未対応
レイアウト
| SwiftUI | Expo (React Native) | Asuka | |
|---|---|---|---|
| スタック、間隔、パディング、配置 | HStack、VStack、Spacer |
flexbox | ✓ Row、Column、Style(flexbox:Grow、Gap、Align、Justify、Wrap) |
| オーバーレイ、絶対配置 | ZStack、.overlay |
position: absolute |
✓ Style.Absolute、Top/Left/... |
| スクロール | ScrollView |
ScrollView |
✓ Scroll(縦、横。画面外の行はツリーから外れる) |
| 遅延リスト、セルの再利用 | List、LazyVStack |
FlatList |
✓ List(プラットフォームのもの:UICollectionView、RecyclerView。Mac、Windows、Linux、Web では、表示中の行をホストがバインドして再利用) |
| グリッド | LazyVGrid |
FlatList numColumns |
✓ ListProps.Columns、Gap |
| チャットのリスト:末尾から始め、古いものを上に読み込む | defaultScrollAnchor(.bottom)、scrollPosition |
FlatList inverted、maintainVisibleContentPosition |
✓ ListProps.Anchor、OnStartReached。どのリストでも、表示中の部分より上に行を追加しても表示位置は動かない |
| セクション、固定ヘッダー | List の中の Section |
SectionList、stickySectionHeadersEnabled |
✓ ListProps.Section、Header(plain と bare のリストでは固定) |
| 行の選択 | List(selection:)、EditButton |
コミュニティ製 | ✓ ListProps.Selecting、Selected、OnSelect、OnSelectBegin(iOS の編集モードと 2 本指のパン、Android のチェックボックスと長押し) |
| セーフエリア、キーボード | 自動 | SafeAreaView、KeyboardAvoidingView |
✓ SafeArea、UseEnvironment().Keyboard |
| ビューのサイズの取得 | GeometryReader |
onLayout |
✓ ViewProps.OnLayout |
| アスペクト比、最小・最大サイズ | .aspectRatio、.frame(min:max:) |
スタイル | ✓ Style |
コントロール
| SwiftUI | Expo | Asuka | |
|---|---|---|---|
| テキスト、装飾付きテキスト、リンク | Text、AttributedString |
Text |
✓ Text、RichText(スパン、リンク、インラインのビュー、選択)、asuka/sdk/markdown |
| ボタン(バリエーション、アイコン、メニュー) | Button、Menu |
Pressable |
✓ Button(filled、tinted、gray、bordered。アイコン。メニュー)、Menu、コンテキストメニュー。メニュー項目の無効化、チェック、サブメニュー、タイトル付きのセクション(Action.Disabled、Checked、Children、Inline。ui.Submenu、ui.MenuSection)、メニューを開くバーボタン |
| テキストフィールド | TextField、SecureField、TextEditor |
TextInput |
✓ TextField(パスワード入力、キーボードの種類、リターンキー、複数行) |
| カーソルとその位置への挿入 | TextSelection |
selection、onSelectionChange |
✓ TextFieldProps.Selection、ui.InsertText |
| リッチテキストの編集(メールの本文) | AttributedString を使う TextEditor(iOS 26) |
コミュニティ製(10tap、pell) | ✓ TextFieldProps.Rich、ToggleFormat、SetLink、OnFormats |
| キーボードの上のバー | .toolbar { ToolbarItemGroup(placement: .keyboard) } |
InputAccessoryView(iOS) |
✓ TextFieldProps.Accessory(両方) |
| 貼り付けた画像、キーボードの GIF とステッカー | onPasteCommand、PasteButton |
コミュニティ製(onPaste) |
✓ TextFieldProps.PasteTypes、OnPaste(Android の commitContent にも対応) |
| トグル、スライダー | Toggle、Slider |
コミュニティ製 | ✓ Toggle、Slider |
| セグメンテッドコントロール | Picker(.segmented) |
コミュニティ製 | ✓ Segmented |
| 日付と時刻 | DatePicker |
コミュニティ製 | ✓ DatePicker(日付、時刻、両方) |
| 進捗、スピナー | ProgressView |
ActivityIndicator |
✓ Progress、ProgressRing(円形。値あり・なし)、Activity |
| ホイールとメニューのピッカー、ステッパー | Picker、Stepper |
コミュニティ製 | ✓ Wheel、Picker(iOS と Mac ではポップアップボタン、Android では Material の exposed dropdown menu)、Stepper |
| カラーピッカー | ColorPicker |
コミュニティ製 | ✓ ColorPicker(iOS ではシステムのピッカー。システムのピッカーがない Android では、Google のパレットを並べた Material のダイアログで、不透明度も指定可能) |
| アイコン | SF Symbols | @expo/vector-icons |
✓ Symbol(iOS と Mac では SF Symbols。同じ名前で、Android と Web では Material Symbols、Windows では Segoe Fluent Icons、Linux ではアイコンテーマのものか Material Symbols。厳選したセット) |
| 画像(リモート、キャッシュ、プレースホルダー) | AsyncImage |
expo-image |
✓ Image(メモリとディスクのキャッシュ、Placeholder) |
| 動画 | VideoPlayer |
expo-video |
✓ Video(iOS と Mac では AVKit、Android では Media3、Windows では WebView2、Linux では GStreamer、Web では <video>) |
| Web コンテンツ | WKWebView |
react-native-webview |
✓ WebView(iOS と Mac では WKWebView、Android の WebView、Windows では WebView2、Linux では WebKitGTK。URL、HTML、双方向のメッセージ、ページより先に実行するスクリプト、リクエストのブロック、状態と履歴、ダウンロード、新しいウィンドウ、Cookie、スクリプトの値。Android では個々のリクエストも。Web では、アプリ自身のサイトと postMessage で協調するページならこれらすべてに対応し、ほかのサイトのページは読み込むだけ)。asuka/sdk/js は画面外で JavaScript を実行 |
| 地図 | Map |
expo-maps |
✓ asuka/sdk/maps(Apple Maps。API キーがあれば Google Maps:表示範囲、マーカー、ユーザーの現在地、タップ。Linux では libshumate の OpenStreetMap の地図。Windows ではホストが OpenStreetMap のタイルを自分で描画) |
| 独自の描画 | Shape、Path、Canvas |
react-native-svg、Skia |
✓ Shape と Path(塗り、線、線端、角の形、破線、アニメーション可能なトリム)。Canvas(パス、グラデーション、テキスト、変形、クリップ、不透明度)。SVG アイコンのネイティブ描画(asuka/sdk/svg) |
| グラフ | Swift Charts | victory-native、コミュニティ製 |
✓ asuka/sdk/chart(棒、線、面、点) |
| シェーダー | .colorEffect(ShaderLibrary...) |
Skia の runtime effects | ✓ Shader(Kage。Go の構文で、iOS と Mac では Metal、Android では AGSL、Web では WebGL 2、Windows では Direct3D 11、Linux では OpenGL) |
| ネイティブへの抜け道 | UIViewRepresentable |
ネイティブモジュール | ✓ ネイティブパッケージ:Go で宣言した型付きのモジュールとビューを、iOS と macOS では Swift、Android では Kotlin、Web では JavaScript、Windows と Linux では Go で実装(ネイティブコード) |
| ページング、ページのドット | .scrollTargetBehavior(.paging)、TabView(.page) |
react-native-pager-view |
✓ ScrollProps.Paging、PageControl |
| チェックボックス | Toggle(.checkbox、macOS) |
コミュニティ製 | ✓ ToggleProps.Checkbox |
ナビゲーションと表示
| SwiftUI | Expo (expo-router) | Asuka | |
|---|---|---|---|
| ネイティブのプッシュ、ポップ、戻るジェスチャーを備えたスタック | NavigationStack |
Stack | ✓ Stack、UseNavigator |
| タブ | TabView |
Tabs | ✓ Tabs |
| サイドバー、ドロワー | NavigationSplitView |
Drawer | ✓ SplitView(iPhone ではスタック、Android ではドロワー。Mac では NSSplitViewController、Windows ではナビゲーションペイン、Linux では libadwaita のスプリットビュー) |
| タブとサイドバーの行のバッジ | badge |
tabBarBadge、drawerItem のカウント |
✓ Tab.Badge、Destination.Badge(badgeValue、BadgeDrawable、ドロワーのカウンター。Mac では NSTabView のラベルとソースリストのカウント。Fluent の InfoBadge。libadwaita の badge-number) |
| バーボタン、タイトル、検索 | .toolbar、.searchable |
ヘッダーのオプション | ✓ ScreenOptions(Actions、Search) |
| 下部のツールバー、主要なアクション(FAB) | ToolbarItem(placement: .bottomBar) |
— | ✓ ScreenOptions(Toolbar、Primary、PrimaryExtended):UIKit では末尾に目立つ項目を置いたツールバー、Material では BottomAppBar と(extended)FAB、Mac ではウィンドウのツールバー、Windows ではコマンドバー、Linux では GtkActionBar、Web ではバーとフローティングボタン |
| デテントのあるシート | .sheet、.presentationDetents |
モーダル | ✓ Sheet(Detents、Grabber) |
| 全画面のモーダル | .fullScreenCover |
presentation: "fullScreenModal" |
✓ FullScreen を指定した Sheet(iOS の .fullScreen。Material の全画面ダイアログで、エッジツーエッジ、予測型「戻る」に対応。デスクトップではウィンドウのコンテンツ全体を覆うシートまたはウィンドウ。Web ではページを覆う <dialog>) |
| 戻る操作の横取り、閉じるのを防ぐ | .navigationBarBackButtonHidden、.interactiveDismissDisabled |
BackHandler、usePreventRemove |
✓ OnBack(画面、シート、アプリ) |
| ポップオーバー | .popover |
— | ✓ Popover(iPhone でもポップオーバー) |
| アラート、アクションシート | .alert、.confirmationDialog |
Alert |
✓ Alert(両方のスタイル。アラートのテキストフィールド:Fields、パスワード用の Secure、Disabled で入力に応じて有効になるボタン) |
| スナックバー、トースト(「元に戻す」付きの短いメッセージ) | — | コミュニティ製(react-native-paper の Snackbar) |
✓ Snackbar(Android では Material のもの、Linux では AdwToast。iOS、macOS、Windows、Web ではプラットフォームらしい見た目で描画) |
| ディープリンク、ユニバーサルリンクと App Links | .onOpenURL |
Linking | ✓ OnOpenURL、asuka.json の scheme と links |
| 引っ張って更新、スワイプアクション | .refreshable、.swipeActions |
RefreshControl |
✓(両端:SwipeActions、LeadingSwipeActions) |
モーション
| SwiftUI | Expo (Reanimated) | Asuka | |
|---|---|---|---|
| 変化のアニメーション | withAnimation |
withTiming、withSpring |
✓ Animate、AnimateWith(イージング、リニア、スプリング。繰り返し、自動反転) |
| 変形 | .rotationEffect、.scaleEffect、.offset |
transform |
✓ Style.Transform |
| 出現と退場のトランジション | .transition |
entering/exiting | ✓ Style.Transition(Fade、Pop、SlideUp、独自のもの) |
| フレーム駆動のアニメーション | TimelineView(.animation) |
useFrameCallback |
✓ OnFrame |
| 影、グラデーション、ぼかし | .shadow、LinearGradient、マテリアル |
スタイル、expo-linear-gradient、expo-blur | ✓ Shadow、LinearGradient、Material |
| ジェスチャー | タップ、長押し、ドラッグ、拡大、回転 | Gesture Handler | ✓ タップ、長押し、パン、ピンチ、回転、スワイプ |
| ハードウェアキーボードのキー | .onKeyPress、.keyboardShortcut |
onKeyPress(TextInput のもの。React Native for Windows and macOS では onKeyDown) |
✓ ViewProps.Keys、TextFieldProps.Keys(フォーカスのあるビューから外側へ、コントロールやメニューより先に処理)、UseKeys(ウィンドウ全体) |
| 押下とホバーの状態 | ButtonStyle の isPressed、.onHover |
Pressable の pressed、onHoverIn/onHoverOut |
✓ Style.Pressed、Hovered のバリアント。グループやピアの状態には Interaction(Style.When)。OnPressedChange、OnHoverChange |
| ドラッグ&ドロップ | .draggable、.dropDestination |
(組み込みなし) | ✓ Drag、Drop(アプリ内では Go の値。ほかのアプリとはテキスト、リンク、ファイル。Dragging と DragOver のバリアント) |
| 共有要素のトランジション | matchedGeometryEffect、.navigationTransition(.zoom) |
shared transitions | ✓ Style.Match(ビューが別のビューの位置とサイズから広がる)、ScreenOptions.ZoomFrom(プッシュした画面がビューからズームして現れる:iOS 18 のズーム、Material の container transform) |
| 触覚フィードバック | sensoryFeedback |
expo-haptics |
✓ Haptic |
| 視差効果を減らす | accessibilityReduceMotion |
useReducedMotion |
✓ Environment.ReduceMotion(iOS ではトランジションがディゾルブになる) |
データとサービス
| SwiftUI / Apple | Expo | Asuka | |
|---|---|---|---|
| 状態 | @State、@Observable |
フック | ✓ シグナル(細粒度:書き込むと、その値を読んでいるところだけが更新される) |
| 永続化する小さな値 | @AppStorage |
AsyncStorage | ✓ asuka/sdk/store |
| SQLite | SwiftData / GRDB | expo-sqlite |
✓ asuka/sdk/sqlite(マイグレーション、型付きのクエリ、トランザクション、メインスレッド外での書き込み、読んでいるテーブルに書き込みがあると再実行されるライブクエリ。変化する引数にも対応:WatchWith。ブラウザーでも、SQLite の WebAssembly ビルドで動作) |
| セキュアストレージ | キーチェーン | expo-secure-store |
✓ asuka/sdk/secure(Web では、ブラウザーが外に出さない鍵で暗号化して IndexedDB に保存。キーチェーンではない) |
| ファイル、ネットワーク | Foundation | expo-file-system、fetch |
✓ Go の os、net/http |
| 写真 | PhotosPicker |
expo-image-picker |
✓ device.PickPhotos |
| 書類 | UIDocumentPickerViewController |
expo-document-picker |
✓ device.PickFiles(MIME タイプ、複数選択) |
| カメラ | AVFoundation | expo-camera |
✓ asuka/sdk/camera:Take(システムのカメラ。Mac、Windows、Linux ではライブプレビュー付きのダイアログ。Windows は Media Foundation のキャプチャーエンジン、Linux はカメラポータルか GStreamer 経由の Video4Linux)、View(前面か背面のライブプレビュー)、Capture。asuka/sdk/camera/record で音声付きの動画を録画 |
| バーコード、QR コード | AVFoundation、Vision | expo-camera のスキャン |
✓ asuka/sdk/scan:camera.View でのライブ読み取り(Scan: scan.Codes(...))と画像ファイルからの読み取り(scan.File)。Android では ML Kit で、インポートしたアプリにだけ含まれる。Linux では ZBar。Windows では Go で書いた Asuka 独自のリーダー(internal/barcode:QR、Data Matrix、EAN、UPC、Code 128、Code 39) |
| 位置情報 | Core Location | expo-location |
✓ asuka/sdk/location(地名。Windows では OpenStreetMap の Nominatim) |
| 翻訳、複数形、地域のフォーマット | String Catalogs、FormatStyle | expo-localization、i18next |
✓ asuka/i18n、カタログ(多言語対応) |
| 右から左のレイアウト | layoutDirection |
I18nManager |
✓ 言語に従う、ui.WithDirection |
| ローカル通知 | UserNotifications | expo-notifications |
✓ asuka/sdk/notify(即時またはスケジュール、画像。iOS、Android、macOS、Linux。Windows ではトーストで、アプリの COM アクティベーターを通じてボタンと返信に対応。Web ではアプリの Service Worker を通じて表示するので、ページを閉じたあとも残り、操作に応答する。ブラウザーは通知をスケジュールできないため、スケジュールした通知はアプリのページが開いていればその時刻に、開いていなければ次にページを開いたときに表示) |
| プッシュ通知 | APNs | expo-notifications |
✓ asuka/sdk/push(iOS と macOS では APNs、Firebase Cloud Messaging、Web では VAPID による Web Push:トークン、受信したメッセージとタップされたメッセージ。asuka/sdk/push/webpush で Go のサーバーから Web Push を送信) |
| 通知のアクション、インライン返信 | UNNotificationCategory、UNTextInputNotificationAction |
setNotificationCategoryAsync |
✓ notify.SetCategories、OnAction(バックグラウンドのアクションはアプリの Go のコードを実行し、アプリが動いていなければ画面なしで起動する。Response.Defer。Android では返信を会話に投稿し直す。Web では Service Worker の通知アクションで、Chrome が対応していればインライン返信も) |
| スレッド、表示中の通知、バッジ | threadIdentifier、getDeliveredNotifications、setBadgeCount |
getPresentedNotificationsAsync、setBadgeCountAsync |
✓ Thread(Android ではサマリーを保ったグループ)、notify.Showing、Remove、RemoveThread、SetBadge(Android では通知に基づくランチャーのバッジ) |
| フォアグラウンドでの通知 | willPresent |
setNotificationHandler |
✓ notify.Present(バナー、バナーと音なしで通知一覧のみ、非表示) |
| Android の通知チャンネル | — | setNotificationChannelAsync |
✓ notify.SetChannels、SetChannelGroups。プッシュ通知の channel_id(iOS にはない) |
| 会話のメッセージ | コミュニケーション通知(INSendMessageIntent) |
コミュニティ製 | ✓ Notification.From、push.Message.From:iOS のコミュニケーション通知(使うアプリにだけ entitlement を追加)、Android の MessagingStyle と会話のショートカット。macOS では通常の通知。バブルには非対応 |
| 表示前のプッシュ通知の書き換え | Notification Service Extension | コミュニティ製(expo-notifications には機能なし) |
✓ push.Rewrite:iOS では拡張機能の中で(asuka build がビルドと署名を行う)、Android のデータメッセージではアプリのプロセスの中で、アプリの Go のコードを実行 |
| クリップボード、共有シート、ブラウザー | UIPasteboard、ShareLink、SFSafariViewController |
expo-clipboard、expo-sharing、expo-web-browser |
✓ Copy、device.Clipboard(テキスト。画像は CopyImage、ClipboardImage。ClipboardHas、OnClipboardChange)、Share(テキスト)、ShareFiles(ファイル。expo-sharing と同じく MIME タイプ、UTI、タイトル、iPad のアンカーを指定可能。シートが閉じたら完了)、device.Browse |
| ファイルのプレビュー | QLPreviewController、.quickLookPreview |
react-native-file-viewer |
✓ asuka/sdk/preview(iOS と Mac では Quick Look で、複数ファイルとマークアップに対応。Android ではビューアーアプリ。Windows と Linux ではデフォルトのアプリ) |
| 印刷、HTML からの PDF 作成 | UIPrintInteractionController、UIPrintPageRenderer |
expo-print |
✓ asuka/sdk/print(ファイル、HTML、WebView を、システムのダイアログで印刷し、その結果も取得。PDF:ページ分割、地域に合った用紙、余白) |
| メールとメッセージの作成画面 | MFMailComposeViewController、MFMessageComposeViewController |
expo-mail-composer、expo-sms |
✓ asuka/sdk/compose(宛先、件名、HTML の本文、添付ファイル。iOS ではユーザーが何をしたかも取得。Android ではメールアプリのみ。Mac ではメッセージ) |
| スリープの抑止、デバイス情報 | isIdleTimerDisabled、UIDevice |
expo-keep-awake、expo-device |
✓ device.KeepAwake、device.GetInfo |
| ネットワークの状態 | NWPathMonitor |
expo-network |
✓ asuka/sdk/network(オンラインかどうか、Wi-Fi かモバイル通信か、従量制かどうか、データセーバー/省データモード) |
| ギャラリーや Downloads への保存 | PHPhotoLibrary |
expo-media-library |
✓ asuka/sdk/library(MediaStore のコレクション。写真、または「ファイル」の Documents) |
| バックグラウンドでの処理 | beginBackgroundTask、BGTaskScheduler |
expo-task-manager、expo-background-task |
✓ asuka/sdk/background:Android ではアプリを動かし続け、進捗とボタンを通知に表示。iOS では約 30 秒。Mac では動かし続け(App Nap をオフ)、Linux と Windows ではシステムのスリープを防ぐ。asuka/sdk/periodic:システムがときどき実行するタスクで、アプリの Go のコードを画面なしで起動(BGTaskScheduler、WorkManager。Mac ではアプリの実行中に NSBackgroundActivityScheduler。Linux と Windows では実行中のタイマー) |
| ほかのアプリからの共有の受け取り | 共有拡張機能 | expo-sharing(useIncomingShare) |
✓ ui.OnShare:テキスト、リンク、画像、動画、音声、ファイルを、一つでも複数でも、名前、MIME タイプ、サイズ付きで受け取る(asuka.json の shareTarget:types と max から、Android のインテントフィルターと iOS のアクティベーションルールを生成。iOS ではアプリを開く共有拡張機能で、ファイルは App Group 経由)。app.ShareSheet は、共有元のアプリの上に出すアプリ独自の画面(iOS では共有拡張機能、Android では独立したプロセスのボトムシート)で、データをアプリと共有する。リンクの解決は行わない(net/http を使う) |
| 音声 | AVAudioPlayer、AVAudioRecorder | expo-audio |
✓ asuka/sdk/audio(読み込み、再生、一時停止、シーク、音量、ループ)。asuka/sdk/audio/record(音声向けまたは音楽向けの音質、一時停止、レベルメーター) |
| 再生中の情報、ロック画面のコントロール、メディアキー | MPNowPlayingInfoCenter、MPRemoteCommandCenter |
expo-audio のロック画面のコントロール、コミュニティ製(react-native-track-player) |
✓ asuka/sdk/nowplaying(タイトル、アーティスト、アルバム、アートワーク、長さ、システムが進める再生位置、再生、一時停止、次へ、前へ、シーク、スキップ、停止。設定したハンドラーに応じて有効化。audio.Player には自動で追従。Android では MediaSession とメディア再生サービス、Mac ではコントロールセンター、Windows ではメディアのフライアウト、Linux では MPRIS、Web では Media Session API) |
| Sign in with Apple | SignInWithAppleButton |
expo-apple-authentication |
✓ asuka/sdk/appleauth(Apple のボタン、認証情報、その状態。iOS と macOS) |
| Face ID、指紋認証 | LocalAuthentication | expo-local-authentication |
✓ asuka/sdk/biometric(Windows では Windows Hello。Web では WebAuthn によるプラットフォームの認証器) |
| ストアのレビュー | requestReview |
expo-store-review |
✓ asuka/sdk/review(App Store のプロンプト、Google Play のアプリ内レビュー) |
| トラッキングの許可 | App Tracking Transparency | expo-tracking-transparency |
✓ asuka/sdk/tracking |
| アプリの完全性 | App Attest | expo-app-integrity |
✓ asuka/sdk/integrity(App Attest、Play Integrity) |
| 年齢層 | Declared Age Range | expo-age-range |
✓ asuka/sdk/agerange(Declared Age Range、Play Age Signals) |
| センサー | Core Motion | expo-sensors |
✓ asuka/sdk/sensors(加速度計、ジャイロスコープ、磁力計、姿勢と方位を含むデバイスのモーション、気圧計、照度、歩数計。両 OS で同じ軸と単位。Mac と Windows にはセンサーなし。Linux では iio-sensor-proxy の照度と向き) |
| 画像の編集 | Core Graphics、ImageIO | expo-image-manipulator |
✓ asuka/sdk/imageedit(リサイズ、切り抜き、回転、反転、塗りつぶし、モザイク。HEIC を読み込んで JPEG か PNG で出力。向きを正し、元画像のメタデータは含めない。Linux では GdkPixbuf のデコーダー、Windows では WIC のデコーダー) |
| 画像の中のテキスト、顔、コード | Vision | なし | ✓ asuka/sdk/vision(それぞれの位置付き)。Android では ML Kit で、インポートしたアプリにだけ含まれる。Linux では ZBar のコードと Tesseract のテキストで、顔は非対応。Windows では OCR のテキストと顔、Asuka のリーダーのコード |
| 写真ライブラリ | PhotoKit | expo-media-library |
✓ asuka/sdk/photos:許可、一覧、サムネイル、元のファイル(Windows ではピクチャフォルダー) |
| 動画のサムネイル | AVAssetImageGenerator |
expo-video-thumbnails |
✓ imageedit.VideoFrame |
| 音声合成 | AVSpeechSynthesizer |
expo-speech |
✓ asuka/sdk/speech(キュー、音声の種類、ピッチ、速さ、単語の区切りでの一時停止) |
| 連絡先 | Contacts、ContactsUI | expo-contacts |
✓ asuka/sdk/contacts(許可なしでピッカーと新規連絡先の画面。許可があればアドレス帳。Windows では追加すると vCard を開き、アドレス帳はなし) |
| カレンダー | EventKit、EventKitUI | expo-calendar |
✓ asuka/sdk/calendar(許可なしでイベントの編集画面。許可があればカレンダーとイベント。Windows では追加すると iCalendar ファイルを開き、カレンダーはなし) |
| ホーム画面ウィジェット | WidgetKit | expo-widgets |
✓ asuka/sdk/widget と asuka.json の widgets(すべてのサイズのテンプレート、iOS ではロック画面のものも。タイムライン、カウントダウン、URL 付きのタップ) |
| ライブアクティビティ | ActivityKit | expo-live-activity |
✓ asuka/sdk/liveactivity(iOS ではロック画面と Dynamic Island。Android では継続中の通知と Android 16 の Live Update。カウントダウン、進捗、アラート、サーバーからのプッシュによる更新と開始) |
| App Intents、ショートカット | App Intents、App Shortcuts | — | ✓ asuka.json の shortcuts(iOS では Siri のフレーズ付きの App Shortcuts で、Spotlight とショートカットアプリに表示。Android ではランチャーのショートカット) |
| カスタムフォント | .font(.custom) |
expo-font |
✓ Font.Family |
| ライフサイクル | scenePhase |
AppState | ✓ app.OnLifecycle |
| 起動画面 | UILaunchScreen |
expo-splash-screen |
✓ asuka.json の splash。asuka/sdk/splash でアプリの読み込み中は表示を保持 |
| ステータスバー、ナビゲーションバー | statusBarHidden、preferredStatusBarStyle |
expo-status-bar、expo-navigation-bar |
✓ asuka/sdk/screen の SetBars |
| 画面の向きの固定 | supportedInterfaceOrientations |
expo-screen-orientation |
✓ asuka/sdk/screen の Lock |
| スクリーンショット | isCaptured、userDidTakeScreenshotNotification |
expo-screen-capture |
✓ asuka/sdk/screen の PreventCapture(Android ではキャプチャーが空白になる。iOS では録画中は覆い隠す。macOS と Windows ではウィンドウをキャプチャーから除外)、OnScreenshot(Linux と Windows ではピクチャ/スクリーンショットフォルダーの新しい画像) |
| クラッシュレポート | MetricKit、Crashlytics、Sentry | Sentry、Crashlytics(@react-native-firebase/crashlytics) |
✓ asuka/sdk/crash:Go の panic と致命的なエラー、捕捉されなかった例外、MetricKit のクラッシュとハング、Android のネイティブクラッシュと ANR を、ブレッドクラムと値付きで収集。送り先はアプリが自由に決める(サービスは組み込んでいない)。dSYM とネイティブのデバッグシンボルは asuka build が出力 |
外観とアクセシビリティ
| SwiftUI | Expo | Asuka | |
|---|---|---|---|
| ダークモード、セマンティックカラー、アクセントカラー | 自動 | useColorScheme |
✓ セマンティックカラー、asuka.json の accent(Android では Material 3 のダイナミックカラー。Mac では AppKit 自身のコントロールはユーザーのアクセントカラーのまま) |
| Dynamic Type、フォントサイズ | 自動 | allowFontScaling |
✓ テキストスタイルもカスタムフォントも拡大縮小される |
| スクリーンリーダーのラベル、ロール、非表示 | .accessibilityLabel... |
accessibilityLabel... |
✓ Label、Hint、Role、Decorative。押せるビューはボタンになる |
| 読み上げ | AccessibilityNotification.Announcement |
announceForAccessibility |
✓ Announce |
| スクリーンリーダーのフォーカスの移動 | @AccessibilityFocusState |
setAccessibilityFocus |
✓ AccessibilityFocus |
| スクリーンリーダーが有効かどうか | voiceOverEnabled |
isScreenReaderEnabled |
✓ Environment.ScreenReader |
| カスタムアクション、調整可能なビュー | .accessibilityAction、.accessibilityAdjustableAction |
accessibilityActions |
✓ ViewProps.Actions、OnAdjust(List のスワイプアクションも TalkBack のアクションになる) |
ツール
| Xcode / Expo | Asuka | |
|---|---|---|
| ライブリロード、状態の保持 | プレビュー、Fast Refresh | ✓ asuka dev:保存するとリロードし、シグナルの値を保持(スマートフォンでは Go のコードは手元のコンピューターで動く。デスクトップではアプリをビルドし直して再実行。ブラウザーではページを再読み込み) |
| リリースビルドと署名 | Xcode、EAS Build | ✓ asuka build:.ipa、.aab、署名と公証を済ませたユニバーサルな .app(または Mac App Store 用の .pkg)、署名済みの .exe を含む Windows のフォルダー、.tar.gz と Flatpak のマニフェストを含む Linux のインストールプレフィックス、静的サイト(配布) |
| ストアへのアップロード | Xcode、EAS Submit | ✓ asuka publish:TestFlight、App Review、Google Play のトラック。Mac App Store にはまだ非対応 |
| GUI のテスト | XCUITest、Detox、Maestro | ✓ e2e/:iOS シミュレーター、Android エミュレーター、Xvfb 上の Linux、ヘッドレス Chrome でサンプルを実行。Mac と Windows のホストのテストは、実際の AppKit のビューと Win32 のウィンドウを操作する |
| ベンチマーク | Instruments | ✓ bench/(SwiftUI、Compose との比較) |
| 既存のアプリへの組み込み | — | ブラウンフィールド(React Native のもの) |
| OTA アップデート | — | EAS Update:✗ |
デスクトップのフレームワーク
Mac、Windows、Linux では、HTML を Web ビューで包んだアプリもよく作られます。そうしたフレームワークと Asuka を比べると次のようになります。
| Electron | Tauri | Wails | React Native for Windows and macOS | Asuka | |
|---|---|---|---|---|---|
| プラットフォーム | Mac、Windows、Linux | Mac、Windows、Linux。iOS と Android(Tauri 2) | Mac、Windows、Linux | Windows、Mac(React Native の iOS、Android と合わせて) | Mac、Windows、Linux、iOS、Android、Web |
| UI を表示するもの | Chromium の中の HTML と CSS | システムの Web ビュー(WKWebView、WebView2、WebKitGTK)の中の HTML と CSS | システムの Web ビューの中の HTML と CSS | プラットフォームのネイティブのビュー | 各プラットフォーム純正のウィジェット:AppKit、Windows 11 風に描画した Win32 とコモンコントロール、GTK 4 と libadwaita |
| アプリの言語 | JavaScript(ページとは別に Node.js) | Rust と、ページ内の JavaScript | Go と、ページ内の JavaScript | JavaScript | Go |
| アプリと一緒に配布するもの | Chromium と Node.js | アプリのバイナリ(Web ビューはシステムのもの) | アプリのバイナリ(同上) | JavaScript エンジンと React Native のランタイム | アプリの Go のバイナリ(Linux では GTK 4 と libadwaita はシステムのもの)。アプリが WebView を表示しない限り Web ビューは含まない |
Web ビューのアプリは、HTML と CSS で書いたとおりに、どのシステムでも同じ見た目になり、Web のライブラリを使えます。Asuka のアプリは各プラットフォームのアプリと同じ見た目になり(スタイルを設定しないアプリは、Mac では Mac のアプリ、Linux では GNOME のアプリ、Windows では Windows 11 のアプリになります)、そのプラットフォームのアクセシビリティ、テキスト入力、メニューをそのまま使えます。しかも、レイアウトエンジンは Go で書かれた一つだけで、すべてのプラットフォームに共通です。デスクトップのホストは、アプリの Go のコードをアプリ自身のプロセスで実行し、cgo を使わずにプラットフォームを呼び出します。そのため asuka build は、どのシステムからでも Windows と Linux のビルドを作れます。
デスクトップアプリの機能
デスクトップアプリがウィンドウとメニューバー以外に必要とする機能を、Electron、MyGo と比べます(デスクトップ)。行に特に記載がない限り、✓ は Mac、Windows、Linux で使えることを表します。
| Electron | MyGo | Asuka | |
|---|---|---|---|
| ウィンドウの制御 | BrowserWindow |
Window |
✓ ui.UseWindow、ui.MainWindow:表示、非表示、フォーカス、最小化、最大化、フルスクリーン、サイズ、位置、常に手前に表示、状態をシグナルで取得。OnCloseRequest。StateKey による前回の位置の復元。子ウィンドウとモーダルウィンドウ。タイトルバーの非表示(Wayland では位置の指定、中央への配置、常に手前に表示はできず、コンポジターが決める。X11 では Xlib で実現) |
| トレイアイコン | Tray |
トレイアイコン | ✓ tray.Show(NSStatusItem、通知領域、Linux では StatusNotifierItem):アイコンまたはシンボル、タイトル、ツールチップ、メニュー、クリック |
| サブメニュー、終了の項目 | MenuItem.submenu、ロール |
ロール、サブメニュー | ✓ MenuItem.Submenu、ui.MenuQuit。メニューバー、トレイ、Dock のメニューで使える |
| Dock のメニュー、ジャンプリストのタスク | app.dock.setMenu、setUserTasks |
Dock のメニュー、ジャンプリスト | ✓ tray.DockMenu(Dock のメニュー、ジャンプリストのタスク、Linux のランチャーのクイックリスト) |
| バックグラウンドのアプリ | app.dock.hide、window-all-closed で終了しない |
バックグラウンドのアプリ | ✓ app.KeepRunning、app.RunInBackground、asuka.json の background |
| フライアウト:トレイアイコンから垂れ下がるウィンドウ | アプリが tray.getBounds() の位置に置くフレームなしの BrowserWindow |
フライアウト | ✓ WindowProps.Flyout。tray.Icon.Anchor() か、ウィンドウ内の矩形から垂れ下がる:ディスプレイに収まるように配置・反転し、ほかの場所のクリックか Escape で閉じ、上にモーダルダイアログも出せる(Linux では X11 の場合のみ配置) |
| ランチャー用のパネル | type: 'panel'(macOS) |
パネル | ✓ Panel:すべてのアプリと操作スペースの上に浮かび、Mac ではアプリをアクティブにせずにキーボード入力を受け、Escape かほかの場所のクリックで閉じる(Linux の Wayland では通常のウィンドウ) |
| コンテンツに合わせたウィンドウのサイズ | アプリが setContentSize を呼ぶ |
FitContent |
✓ FitContent:コンテンツが変わると、コアのレイアウトがウィンドウのサイズを決める |
| クリックを透過するウィンドウ | setIgnoreMouseEvents |
SetIgnoreMouseEvents、SetInputRegion |
✓ ClickThrough、WindowHandle.SetClickThrough(ウィンドウ全体) |
| ランチャー向けのインストール済みアプリ | なし | InstalledApps、FileIcon、LaunchApp |
✓ asuka/sdk/apps の List、Search、Icon、Open |
| グローバルショートカット | globalShortcut |
グローバルショートカット | ✓ hotkey.Register(Carbon のホットキー、RegisterHotKey、X11 のグラブまたは Wayland の GlobalShortcuts ポータル) |
| 開く、保存、フォルダーのダイアログ | dialog.showOpenDialog、showSaveDialog |
ダイアログ | ✓ asuka/sdk/dialog の Open、Save、Folder(Mac ではシート。Web では File System Access のピッカーかファイル入力。スマートフォンでは Open のみ) |
| Finder などでの表示、既定のアプリで開く、ゴミ箱に移動 | shell.showItemInFolder、openPath、trashItem |
シェル | ✓ asuka/sdk/shell の Reveal、Open、Trash、Beep |
| ファイルの関連付け | fileAssociations(electron-builder)、open-file |
ファイルの関連付け | ✓ asuka.json の fileTypes、ui.OnOpenFiles(iOS、Android、インストールした Web アプリでも) |
| 単一インスタンスと二回目の起動 | requestSingleInstanceLock、second-instance |
単一インスタンス | ✓ app.OnSecondInstance、asuka.json の singleInstance(Mac では引数ではなくファイルを渡す) |
| 終了と、終了前の確認 | app.quit、before-quit |
終了 | ✓ app.Quit、app.OnQuit(Cancel、SessionEnding) |
| アプリのフォルダー | app.getPath |
ディレクトリ | ✓ app.DataDir、ConfigDir、CacheDir、LogDir(すべてのプラットフォーム) |
| 最近使った書類 | app.addRecentDocument |
最近使った書類 | ✓ asuka/sdk/recent(Mac の「最近使った項目を開く」、ジャンプリスト、Linux のユーザーの最近使ったファイル) |
| 書類ベースのアプリ | — | 書類 | ✓ asuka/sdk/document:ファイルごとのウィンドウ、「ファイル」メニュー、自動保存、復元用のコピー、閉じるときと終了するときの保存の確認、ほかのアプリによるファイルの変更(スマートフォンと Web では一度に一つの書類で、その場で保存) |
| ログイン時の起動 | app.setLoginItemSettings |
ログイン時に起動 | ✓ asuka/sdk/login(SMAppService、macOS 13 以降。Run キー。XDG autostart または Background ポータル) |
| ファイルの監視 | Node の fs.watch(chokidar) |
WatchFiles、watch プラグイン |
✓ asuka/sdk/watch の Path、New(FSEvents、inotify、ReadDirectoryChangesW。iOS の kqueue、Android の inotify、Web のアプリ自身のファイルにも対応):名前の変更は対にし、アトミックな保存は書き込みとして扱い、連続したイベントはまとめる |
| ディスプレイ | screen |
ディスプレイ | ✓ screen.UseDisplays、Nearest、Matching(Wayland では作業領域なし。スマートフォンと Web ではそれぞれの画面) |
| スリープ、スリープ解除、ロック、アイドル時間、バッテリー | powerMonitor |
電源 | ✓ asuka/sdk/power(スマートフォンではバッテリー、低電力モード、ロック。Web ではバッテリー) |
| アプリのアイコンでの進捗と注意喚起 | setProgressBar、flashFrame、dock.bounce |
Dock とタスクバー | ✓ dock.SetProgress、RequestAttention(Linux では Unity launcher API に対応したドック) |
| アクセントカラー、コントラストを上げる、透明度を下げる | systemPreferences |
アクセントカラーとアクセシビリティ | ✓ asuka/sdk/appearance(Linux では「透明度を下げる」はなし) |
| 再生中の情報とメディアキー | (なし:ページ内の navigator.mediaSession) |
Now Playing | ✓ asuka/sdk/nowplaying(MPNowPlayingInfoCenter、ウィンドウの SystemMediaTransportControls、MPRIS のプレイヤー) |
| 自動アップデート:署名付きのリリース、差分、Sparkle のウィンドウ | autoUpdater(Squirrel)、electron-updater |
アップデーターとそのプラグイン | ✓ asuka/sdk/update(Window、CheckForUpdates、Check、Install、Relaunch。Ed25519 の署名、差分アップデート、チャンネル。GitHub のリリース、S3 のバケット、任意の HTTPS のディレクトリ、独自のサーバー。asuka publish --platform mac|windows|linux)。システムのパッケージ、Flatpak、ストアでインストールしたアプリは、それらを通じてアップデートする。Web は再読み込み |
MyGo にあって Asuka にまだないものは次のとおりです。書類については、バージョン(Mac の「すべてのバージョンをブラウズ」)とロック。クリックを透過するウィンドウについては、入力領域(ウィンドウのうち、クリックを受け付け続ける部分)の指定です。Asuka のウィンドウは、マウスをウィンドウ全体で透過させるか、まったく透過させないかのどちらかです。
未対応のもの
アプリでの需要が高い順に、パラメーターとエンティティを持つ App Intents(ショートカットはまだどちらも受け付けません)、アプリ独自のレイアウトのウィジェット(いまはテンプレートが一つだけです)、OTA アップデート(EAS Update のようなもの。Asuka のアプリはコンパイルされた Go で、ストアはアプリがそれを置き換えることを認めていません)です。Mac では、ScreenOptions.ZoomFrom のズーム(代わりにプッシュがスライドします)、ジェスチャーによるリストの選択の開始、asuka publish による Mac App Store へのアップロード(.pkg は Transporter でアップロードしてください)が未対応です。