NativeScript

提供: fukudat.net
2020年12月9日 (水) 01:41時点におけるFukudat (トーク | 投稿記録)による版 (→‎インストール)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)
ナビゲーションに移動検索に移動

NativeScript Playground

NativeScript を使い始めるための、ブラウザー上の開発環境。

モバイルデバイスと組み合わせて使う。iOSの場合、

をインストールしておく。

Tutorialで NativeScript の機能を試してみることができる。 モバイルデバイスにインストールした NativeScript Preview で見た目や動作が確認できる。

CLI (コマンドラインインターフェイス)

インストール

Xcode に依存しているので、あらかじめ App Store から Xcode をインストールしておく。

cocoapods, xcodeproj に依存しているので、インストールする。

$ sudo gem install cocoapods xcodeproj

node.js に依存しているので、node.js をインストールする。 Mac の場合は、homebrew を使って、

$ brew install node

でOK。

CLI は npm を使って、

$ npm install --global nativescript

を実行。すると、いろいろ聞いてくるので、適当に答えると、 tns というコマンドが使えるようになる。

なお、2020/5現在、node のバージョンが新しすぎると警告がたくさん出る。nvm で v13.13.0 にセットして黙らせる。

プロジェクトの作成

新しいプロジェクトの名前を "my-project" として、新規に作成するために、次のコマンドを叩く。

$ tns create my-project

すると、2つの質問に順番に答えていく (カーソルキー上下で選ぶ。)

? First, which style of NativeScript project would you like to use:
❯ Angular           |  Learn more at https://nativescript.org/angular 
  Vue.js            |  Learn more at https://nativescript.org/vue 
  Plain TypeScript  |  Learn more at https://nativescript.org/typescript 
  Plain JavaScript  |  Use NativeScript without any framework 
? Next, which template would you like to start from:
  Hello World  |  A Hello World app 
❯ SideDrawer   |  An app with pre-built pages that uses a drawer for navigation 
  Tabs         |  An app with pre-built pages that uses tabs for navigation

ここでは、Angular と SideDrawer を選んでみた。 すると、カレントディレクトリに my-project というディレクトリができて、その中に開発環境が作られる。

この辺りのドキュメントを読みながらアプリを作成する。

Preview

プロジェクトディレクトリに移動して、開発する。

$ cd my-project

Angular を選んだので、src/app のしたに app.component.ts, app.module.ts などのソースコードが展開されている。 これらのファイルをエディットしてアプリを作っていく。

試したくなったら (というか、何も変更しなくても最初からサンプルアプリになっているので)、preview コマンドを叩く。

$ tns preview

すると、QRコードが画面表示されるので、モバイルデバイスにインストールした NativeScript Playground でスキャンする。 しばらくコンパイルに時間がかかったあと、モバイルデバイスが画面遷移して NativeScript Preview 上でアプリが実行される。

これで一応はテストができる。

Simulatorでのテスト

xcode のデバイスシミュレーターでテストする時は、

$ tns run ios

と叩く。

特定のデバイスで実行したいなら、

$ instruments -s devices

を実行すると、Simulator上の様々な種類のデバイスの device id のリストが表示されるので、

$ tns run ios --device <device id>

と叩く。

あとで説明する Developer provisioning profile を作り、そこにリアルデバイス (iPhone, iPad) の device id を登録しておくと、実機でのテストもできる。 同じ instruments コマンドで 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 に従う。 例えば、com.fukudat.<application-name>のようなもの。

これを、package.json ファイルの "nativescript/id" の値に書き込む。

App name

アプリケーションの表示名を決める。アプリケーションアイコンのしたに表示される。

App_Resources/iOS/Info.plistファイルの CFBundleDisplayName key の値として設定する。

App icons

標準の名前でよければ、App_Resoures/iOS のしたにあるicon.png, icon@2x.png, icon@3x.pngを書き換える。 ファイル名を変えたい時は、App_Resources/iOS/Info.plistを以下のように書き換える。

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

これをやってくれる便利なコマンドがあることが判明。まずは画像を images/icon.png に用意して、

$ tns resources generate icons images/icon.png
Generating icons ...
Icons generation completed.

すると以下のファイルが生成される。

  • 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

起動時に表示されるスクリーン。カッコよく作るとアプリの印象が良い。 App_Resoures/iOS/Assets.xcassets/LaunchScreen.Center.imageset のしたにある png ファイルをサイズをそのままに書き換える。

実はこれもコマンドで生成できる。

$ tns resources generate splashes images/icon_transparent.png --background "#123456"

ここで、--backgroundの引数は背景色 (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 とマッチする文字列との組み合わせからなる。 例えば、com.fukudat.* を App ID に選ぶと、com.fukudat. で始まる全ての Bundle ID のアプリケーションとマッチする。

この App ID はあとで provisioning profiles で使用する。

Device

テストで使うデバイスは、https://developer.apple.com/account/resources/devices/list で登録しておく必要がある。

デバイスが接続されている Mac で、

$ instruments -s devices

と叩くと、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 はアプリがアップロードされて、承認申請に送られて、承認されたら更新する。

どちらの値も、App_Resources/iOS/Info.plist に書き込む。

  • CFBundleShortVersionString key に Bundle Short Version String を格納し
  • CFBundleVersion key に Bundle Version String を格納する。

xcode のビルドツールが apple id にアクセスする。2ファクタ認証が設定されている場合、application specific password を設定しておく必要がある。 https://appleid.apple.com/account/manage にアクセスして、tns 用のパスワードを生成する。xxxx-xxxx-xxxx-xxxx のようなパスワードが手に入る。

いよいよビルドする。

$ tns publish ios --appleApplicationSpecificPassword xxxx-xxxx-xxxx-xxxx

と叩く。

これでうまくいくはずなのだが、現在の tns のバージョン (6.5.0) だと、

error: exportArchive: Provisioning profile "<distribution-provisioning-profile-name>" doesn't include signing certificate "Apple Distribution: <YOUR NAME> (<TEAM-ID>)".

(profileにcertificateが含まれていない)というエラーが出て、アプリの提出に失敗してしまう。しかし確かに profile には certificate が含まれている。

いろいろ試したが、次のようにやったらうまくいった。

App_Resources/iOS/build.xcconfig に以下の2行を追加。

PROVISIONING_PROFILE = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;
PROVISIONING_PROFILE_SPECIFIER = <distribution-profile-name>;
$ tns publish ios --provision xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --appleApplicationSpecificPassword xxxx-xxxx-xxxx-xxxx

ただし、--provision の引数に与えたのは distribution provisioning profile の UUIDで、ダウンロードした profile の中に埋まっている。

うまくいくと、

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

てなメッセージが出る。

https://appstoreconnect.apple.com/ に戻り、My App から提出準備中のアプリを開いて、提出したビルドが現れるのを待つ。しばらく(数分)時間がかかる。

あとは、必要な情報を埋めて、右上の提出ボタンを押したら完了。初回の審査には1-2週間かかるらしい。

入力を迷った項目:

  • 暗号を使うか? = REST API を https で呼んでいるので、yes。ただし、https のみを使用にチェック。
  • IDFAを使うか? = 広告を表示するので (自分は使わないが) 広告配信サービスが使うだろうから yes。

Plugin

Geolocation Plugin

インストール

$ tns plugin add nativescript-geolocation

使い方

https://github.com/NativeScript/nativescript-geolocation 参照

NativeScript Plugin for Google Maps

インストール

$ tns plugin add nativescript-google-maps-sdk

使い方

詳細は https://www.npmjs.com/package/nativescript-google-maps-sdk を参照。

app.module.tsに以下のコードを追加:

import * as platform from "platform";
declare var GMSServices: any;

....

if (platform.isIOS) { 
    GMSServices.provideAPIKey("PUT_API_KEY_HERE");
}

地図を使うコンポーネント (例: map.component.*) に以下のようなコードを埋める。

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>
map.component.ts
import { 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;
    ...
  }
}

NativeScript Plugin for Google Admob

Google Admob はモバイルアプリむけの広告配信サービス。 狭いスマホの画面で広告は邪魔だが、on/off をコントロールできたら、使える事もある (かも)。

NativeScript AdMob plugin はこれを実現してくれる。 

Install

$ tns plugin add nativescript-admob

使い方

NativeScript 4.0 以降はApp_Resources/iOS/Info.plist にこれが必要。

  <key>GADApplicationIdentifier</key>
  <string>ca-app-pub-9517346003011652~2508636525</string>

さらに、次のコマンドを叩いておく。

$ pod repo update

Banner広告の出し方。

トップレベルのコンポーネント (app.component.ts) か、Injectable なサービスに以下のコードを追加

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

この createBanner() を呼び出すと広告が表示され、hideBanner() を呼び出すと消える。

bottom: が広告が表示される位置になる。top: と上からの座標を書く事もできる。

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

これを呼び出すと interstitial (区切りの良いところで表示される全面広告) が表示される。

Tips

スクリーンサイズを取得する

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}`);
}

iconを使う

html
<Image src="font://" class="fas t-36">
<Label text="" class="fas t-36"/>
<Label [text]="text" class="fas t-36"/>

iconを program から変更するには、3行目のパターン。ただし、text は &#xXXXX; は使えないので、次のように記述する。

typescript
text = String.fromCharCode(0xf5a0);

icon のコードは https://fontawesome.com/icons?d=gallery で探す。

参考文献