NativeScript はkクロスプラットフォーム はクロスプラットフォーム (iOS と Android) のアプリケーションを構築するフレームワーク。
HTML, CSS, JavaScript を使用して OS native の API にアクセスする。[[Category:How-To]]
インストール方法を含めて、ドキュメントは 公式ドキュメントは https://docs.nativescript.org/ にある。
== [https://play.nativescript.org/ NativeScript Playground] ==
== CLI (コマンドラインインターフェイス) ==
=== インストール ===
Xcode に依存しているので、あらかじめ App Store から Xcode をインストールしておく。
cocoapods, xcodeproj に依存しているので、インストールする。
<pre>
$ sudo gem install cocoapods xcodeproj
</pre>
node.js に依存しているので、node.js をインストールする。
Mac の場合は、[[homebrew]] を使って、
これで一応はテストができる。
=== Simulator/実機でのテスト Simulatorでのテスト ===
xcode のデバイスシミュレーターでテストする時は、
<pre>
$ instruments -s devices
</pre>
を実行すると、device を実行すると、Simulator上の様々な種類のデバイスの device id のリストが表示されるので、
<pre>
$ tns run ios --device <device id>
</pre>
と叩く。リアルデバイス と叩く。 [[#Development provisioning profile|あとで]]説明する Developer provisioning profile を作り、そこにリアルデバイス (iPhone, iPad) を繋いでおくとその の device id も表示されるので、実機でのテストも可能。を登録しておくと、実機でのテストもできる。同じ <code>instruments</code> コマンドで Mac につながっている実機の device id もわかる。 == App Store に載せるまで ==https://docs.nativescript.org/tooling/publishing/publishing-ios-apps を参考にやってみる。 まず Apple の Developer Member になる必要がある。https://developer.apple.com/membercenter で登録する。年間11,800円かかる。 === Bundle ID ===Bundle ID を決める。Bundle ID は reverse domain name notation に従う。例えば、<code>com.fukudat.<application-name></code>のようなもの。 これを、<code>package.json</code> ファイルの "nativescript/id" の値に書き込む。 === App name ===アプリケーションの表示名を決める。アプリケーションアイコンのしたに表示される。 <code>App_Resources/iOS/Info.plist</code>ファイルの <code>CFBundleDisplayName</code> key の値として設定する。 === App icons ===標準の名前でよければ、<code>App_Resoures/iOS</code> のしたにある<code>icon.png</code>, <code>icon@2x.png</code>, <code>icon@3x.png</code>を書き換える。ファイル名を変えたい時は、<code>App_Resources/iOS/Info.plist</code>を以下のように書き換える。<pre> ... <key>CFBundleIconFiles</key> <array> <string>Icon@2x.png</string> <string>Icon.png</string> <string>Icon-Small@3x.png</string> <string>Icon-Small@2x.png</string> <string>Icon-Small.png</string> <string>Icon-Small-50@2x.png</string> <!-- etc --> </array> ...</pre> これをやってくれる便利なコマンドがあることが判明。まずは画像を images/icon.png に用意して、<pre>$ tns resources generate icons images/icon.pngGenerating icons ...Icons generation completed.</pre>すると以下のファイルが生成される。* app/App_Resources/Android/src/main/res/drawable-hdpi/icon.png* app/App_Resources/Android/src/main/res/drawable-ldpi/icon.png* app/App_Resources/Android/src/main/res/drawable-mdpi/icon.png* app/App_Resources/Android/src/main/res/drawable-xhdpi/icon.png* app/App_Resources/Android/src/main/res/drawable-xxhdpi/icon.png* app/App_Resources/Android/src/main/res/drawable-xxxhdpi/icon.png* app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png* app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20.png* app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png* app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png* app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png* app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png* app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png* app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png* app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png* app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png* app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png* app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png* app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png* app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png* app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png === Splash screen ===起動時に表示されるスクリーン。カッコよく作るとアプリの印象が良い。<code>App_Resoures/iOS/Assets.xcassets/LaunchScreen.Center.imageset</code> のしたにある png ファイルをサイズをそのままに書き換える。 実はこれもコマンドで生成できる。<pre>$ tns resources generate splashes images/icon_transparent.png --background "#123456"</pre>ここで、<code>--background</code>の引数は背景色 (default = #FFFFFF (白))。以下のファイルが生成される。* app/App_Resources/Android/src/main/res/drawable-hdpi/background.png* app/App_Resources/Android/src/main/res/drawable-hdpi/logo.png* app/App_Resources/Android/src/main/res/drawable-ldpi/background.png* app/App_Resources/Android/src/main/res/drawable-ldpi/logo.png* app/App_Resources/Android/src/main/res/drawable-mdpi/background.png* app/App_Resources/Android/src/main/res/drawable-mdpi/logo.png* app/App_Resources/Android/src/main/res/drawable-xhdpi/background.png* app/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png* app/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png* app/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png* app/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png* app/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png* app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png* app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png* app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png* app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png* app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png* app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-XR.png* app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-XS-Max.png* app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png* app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png* app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png* app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait-XR.png* app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait-XS-Max.png* app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png* app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png* app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png* app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png* app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png* app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png* app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@3x.png* app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png* app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png* app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@3x.png 素晴らしい。 === Development certificates ===Development certificate は iOS app を開発者が誰かわかるようにサインするために用いられる。 https://developer.apple.com/account/resources/certificates/list を開いて '+' ボタンをクリック。指示に従って 'iOS App Development' certificate を作成する。ダウンロードした certificate file をダブルクリックすると、keychain に格納される。 === Distribution certificate ===Distribution certificate は iTune Connect への申請が誰のものか分かるように、application binary (IPA file) にサインするために用いられる。 https://developer.apple.com/account/resources/certificates/list を開いて '+' ボタンをクリック。指示に従って 'iOS Distribution' certificate を作成する。ダウンロードした certificate file をダブルクリックすると、keychain に格納される。 === Identifiers - App ID ===アプリケーションをデバイス上でテストしたり、App Store に申請するには、App ID が必要となる。App ID は Apple が生成した prefix (Team IDとも呼ばれる) と、自分で決めた Bundle ID とマッチする文字列との組み合わせからなる。例えば、<code>com.fukudat.*</code> を App ID に選ぶと、<code>com.fukudat.</code> で始まる全ての Bundle ID のアプリケーションとマッチする。 この App ID はあとで provisioning profiles で使用する。 === Device ===テストで使うデバイスは、https://developer.apple.com/account/resources/devices/list で登録しておく必要がある。 デバイスが接続されている Mac で、<pre>$ instruments -s devices</pre>と叩くと、device の UDID が表示されるので、それを使って登録する。 === Development provisioning profile ===Development provisioning profile は開発者 (の development certificate) と、App ID と device ID を関連づける。 https://developer.apple.com/account/resources/profiles/list を開いて、'+' ボタンをクリックし、'Development/iOS App Development' を選び、指示に従って新しい development provisioning profile を作成する。これは、app store への申請には必要ないが、実デバイスでのテストに必要となる。 === Distribution provisioning profile ===Distribution provisioning profile には複数の種類がある。App Store での申請に必要な種類は 'App Store Distribution Provisioning Profile' である。Development provisioning profile と同様に、distribution certificate, App ID を関連付ける。 https://developer.apple.com/account/resources/profiles/list を開いて '+' ボタンをクリックし、'Distribution/App Store' を選び、指示に従って新しい distribution provisioning profile を作成する。 === App Store Connect ===https://appstoreconnect.apple.com/ を開いて、'My App' をクリック。'+'ボタンを押して新規Appをクリック。 ウィンドウがポップアップしてくるので、* プラットフォーム = iOS* 名前 = 好きな名前を30文字以内で* プライマリ言語 = 日本語とか英語とか* バンドルID = 上記 Bundle ID の項目で作った ID。選択できる。* SKU = 重複しない名前をつける。Bundle ID と同じような reverse domain notation でも良い。* ユーザアクセス = 制限ありかなしかを入力して、作成をクリック。 さらにApp情報と価格を決めて入力し、保存を押す。 === Build & Upload ===アプリの情報を https://appstoreconnect.apple.com に登録したら、アプリケーションをビルドする。 Bundle Short Version String と Bundle Version String を設定する。* Bundle Short Version String は外部に公開するバージョン '2.1' のような番号で、リリースごとに数字を大きくする。* Bundle Version String は内部的なバージョン番号で、公開バージョンの下に存在する複数のリリース候補を区別するために用いる。例えば '2.1.1', '2.1.2' など。 iTune Connect は同じバージョンで2回アップロードすることはできないので、必ず Bundle Version String を毎回更新する必要がある。 Bundle Short Version String はアプリがアップロードされて、承認申請に送られて、承認されたら更新する。 どちらの値も、<code>App_Resources/iOS/Info.plist</code> に書き込む。* <code>CFBundleShortVersionString</code> key に Bundle Short Version String を格納し* <code>CFBundleVersion</code> key に Bundle Version String を格納する。 xcode のビルドツールが apple id にアクセスする。2ファクタ認証が設定されている場合、application specific password を設定しておく必要がある。https://appleid.apple.com/account/manage にアクセスして、tns 用のパスワードを生成する。<code>xxxx-xxxx-xxxx-xxxx</code> のようなパスワードが手に入る。 いよいよビルドする。<pre>$ tns publish ios --appleApplicationSpecificPassword xxxx-xxxx-xxxx-xxxx</pre>と叩く。 これでうまくいくはずなのだが、現在の tns のバージョン (6.5.0) だと、<pre>error: exportArchive: Provisioning profile "<distribution-provisioning-profile-name>" doesn't include signing certificate "Apple Distribution: <YOUR NAME> (<TEAM-ID>)".</pre>(profileにcertificateが含まれていない)というエラーが出て、アプリの提出に失敗してしまう。しかし確かに profile には certificate が含まれている。 いろいろ試したが、次のようにやったらうまくいった。 App_Resources/iOS/build.xcconfig に以下の2行を追加。<pre>PROVISIONING_PROFILE = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;PROVISIONING_PROFILE_SPECIFIER = <distribution-profile-name>;</pre><pre>$ tns publish ios --provision xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --appleApplicationSpecificPassword xxxx-xxxx-xxxx-xxxx</pre>ただし、<code>--provision</code> の引数に与えたのは distribution provisioning profile の UUIDで、ダウンロードした profile の中に埋まっている。 うまくいくと、<pre>1 packages were uploaded successfully: /tmp/itms-xxxxxx-xxxxx-xxxxxxx.xxxx/mybundle.itmsp[YYYY-MM-DD HH:mm:ss JST] <main> DBG-X: Returning 0</pre>てなメッセージが出る。 https://appstoreconnect.apple.com/ に戻り、My App から提出準備中のアプリを開いて、提出したビルドが現れるのを待つ。しばらく(数分)時間がかかる。 あとは、必要な情報を埋めて、右上の提出ボタンを押したら完了。初回の審査には1-2週間かかるらしい。 入力を迷った項目:* 暗号を使うか? = REST API を https で呼んでいるので、yes。ただし、https のみを使用にチェック。* IDFAを使うか? = 広告を表示するので (自分は使わないが) 広告配信サービスが使うだろうから yes。 == Plugin == === Geolocation Plugin === ==== インストール ====<pre>$ tns plugin add nativescript-geolocation</pre> ==== 使い方 ====https://github.com/NativeScript/nativescript-geolocation 参照 === NativeScript Plugin for Google Maps === ==== インストール ====<pre>$ tns plugin add nativescript-google-maps-sdk</pre> ==== 使い方 ====詳細は https://www.npmjs.com/package/nativescript-google-maps-sdk を参照。 <code>app.module.ts</code>に以下のコードを追加:<pre>import * as platform from "platform";declare var GMSServices: any; .... if (platform.isIOS) { GMSServices.provideAPIKey("PUT_API_KEY_HERE");}</pre> 地図を使うコンポーネント (例: <code>map.component.*</code>) に以下のようなコードを埋める。<pre>map.component.html <GridLayout> <MapView [latitude]="latitude" [longitude]="longitude" [zoom]="zoom" [bearing]="bearing" [tilt]="tilt" [mapAnimationsEnabled]="mapAnimationsEnabled" [padding]="padding" (mapReady)="onMapReady($event)" (markerSelect)="onMarkerSelect($event)" (markerBeginDragging)="onMarkerBeginDragging()" (markerEndDragging)="onMarkerEndDragging()" (markerDrag)="onMarkerDrag()" (cameraChanged)="onCameraChanged()" (cameraMove)="onCameraMove()"></MapView> </GridLayout></pre> <pre>map.component.tsimport { MapView, Marker, Position, Polyline } from "nativescript-google-maps-sdk";...export class MapComonent implements OnInit { private mapView: MapView; onMapRead(event): void { this.mapView = event.object; ... } onMarkerSelect(event): void { const marker = event.marker; const userData = marker.userData; ... }}</pre> === NativeScript Plugin for Google Admob ===[https://admob.google.com/home/ Google Admob] はモバイルアプリむけの広告配信サービス。狭いスマホの画面で広告は邪魔だが、on/off をコントロールできたら、使える事もある (かも)。 [https://www.nsplugins.com/plugin/nativescript-admob NativeScript AdMob plugin] はこれを実現してくれる。 ==== Install ====<pre>$ tns plugin add nativescript-admob</pre> ==== 使い方 ====NativeScript 4.0 以降は<code>App_Resources/iOS/Info.plist</code> にこれが必要。<pre> <key>GADApplicationIdentifier</key> <string>ca-app-pub-9517346003011652~2508636525</string></pre>さらに、次のコマンドを叩いておく。<pre>$ pod repo update</pre> Banner広告の出し方。 トップレベルのコンポーネント (app.component.ts) か、Injectable なサービスに以下のコードを追加<pre>import * as Admob from "nativescript-admob";import { isIOS } from "tns-core-modules/platform"; ... private testing: boolean = true; private iosBannerId = "ca-app-pub-xxxxxxxxxxxxxxxx/xxxxxxxxxx"; private androidBannerId = "ca-app-pub-xxxxxxxxxxxxxxxx/xxxxxxxxxx"; public createBanner() { Admob.createBanner({ testing: this.testing, size: Admob.AD_SIZE.SMART_BANNER, iosBannerId: this.iosBannerId, androidBannerId: this.androidBannerId, iosTestDeviceIds: ["4DC032A6-954E-4AE7-885F-8F32C14C5FAA"], margins: { // top: 50 bottom: 80 } }).then(function() { console.log("admob createBanner done"); }, function(error) { console.log("admob createBanner error: " + error); }); } public hideBanner() { Admob.hideBanner().then(function() { console.log("admob hideBanner done"); }, function(error) { console.log("admob hideBanner error: " + error); }); }</pre>この createBanner() を呼び出すと広告が表示され、hideBanner() を呼び出すと消える。 <code>bottom:</code> が広告が表示される位置になる。<code>top:</code> と上からの座標を書く事もできる。 <pre> public createInterstitial() { admob.createInterstitial({ testing: true, iosInterstitialId: "ca-app-pub-XXXXXX/YYYYY2", // add your own androidInterstitialId: "ca-app-pub-AAAAAAAA/BBBBBB2", // add your own // Android automatically adds the connected device as test device with testing:true, iOS does not iosTestDeviceIds: ["ce97330130c9047ce0d4430d37d713b2"], keywords: ["keyword1", "keyword2"], // add keywords for ad targeting onAdClosed: function () { console.log("interstitial closed") } }).then( function() { console.log("admob createInterstitial done"); }, function(error) { console.log("admob createInterstitial error: " + error); } ); }</pre>これを呼び出すと interstitial (区切りの良いところで表示される全面広告) が表示される。 == Tips ===== スクリーンサイズを取得する ===<pre>import { screen } from "tns-core-modules/platform/platform";...some_method() { console.log(`widthDIPs=${screen.mainScreen.widthDIPs}`); console.log(`widthPixels=${screen.mainScreen.widthPixels}`); console.log(`heightDIPs=${screen.mainScreen.heightDIPs}`); console.log(`heightPixels=${screen.mainScreen.heightPixels}`); console.log(`scale=${screen.mainScreen.scale}`);}</pre> === iconを使う ===<pre>html<Image src="font://" class="fas t-36"><Label text="" class="fas t-36"/><Label [text]="text" class="fas t-36"/></pre> iconを program から変更するには、3行目のパターン。ただし、text は <nowiki>&#xXXXX;</nowiki> は使えないので、次のように記述する。 <pre>typescripttext = String.fromCharCode(0xf5a0);</pre> icon のコードは https://fontawesome.com/icons?d=gallery で探す。 == 参考文献 ==* [https://www.nativescript.org/ NativeScript.org]* [https://www.npmjs.com/package/nativescript-google-maps-sdk NativeScript plugin for the Google Maps SDK] or https://market.nativescript.org/plugins/nativescript-google-maps-sdk* [https://www.nativescript.org/blog/5-plugins-to-monetize-your-nativescript-app 5 Plugins to Monetize Your NativeScript App]* [https://r17n.page/2020/02/20/nativescript-icon-and-splash-screen/ NativeScript で アイコン/スプラッシュスクリーン を 生成/設定]* [http://examination-03.hatenablog.com/ サルでもできるiOSアプリ公開手順~App store 登録~]