中安拓也のブログ

プログラミングについて書くブログ

便利な「ionic cordova resources」コマンドで各種モバイル・タブレット端末のアイコン・スプラッシュスクリーンを作成する〜

Ionicだと、ionic cordova resourcesコマンドで、端末によって様々なサイズが必要になるアイコン・スプラッシュスクリーン画像を一発で作れて便利

バージョン情報

Angular(JavaScriptのWebフレームワーク)ベースの、ハイブリットモバイルアプリ用フレームワークである「Ionic」を使った。

  • Ionic Framework: ionic-angular 3.9.2

  • Node: v8.1.4

  • npm: 5.6.0

  • OS: macOS High Sierra

  • Xcode: Xcode 9.3 Build version 9E145

  • ionic (Ionic CLI) : 3.20.0

  • cordova (Cordova CLI) : 8.0.0

  • Angular: 5.2.9

事前準備

STEP1 ベース画像の用意

ionic cordova resourcesコマンドを実行する前に、アイコン・スプラッシュスクリーンを作る上で、ベースとなる画像が必要になる。具体的には、下記の画像2つをresoucesディレクトリ配下に配置する必要がある。

  • サイズが1024×1024px以上の、アイコン用の画像resources/icon.png

  • サイズが2732×2732px以上のスプラッシュスクリーン用の画像resources/splash.png

用意した画像のサイズが足りないと、ionic cordova resourcesコマンド実行時に下記のエラーが出る。

$ ionic cordova resources
✔ Collecting resource configuration and source images - done!
✔ Filtering out image resources that do not need regeneration - done!
✔ Uploading source images to prepare for transformations - done!
✖ Generating platform resources - failed!
Error: encountered bad status code (400) for https://res.ionic.io/api/v1/transform
body: {"Error":"source image 2208x2208 too small for Default-Portrait@~ipadpro.png, requires at least 2048x2732 source file"}
    at Response.res.on (/usr/local/lib/node_modules/ionic/node_modules/@ionic/cli-utils/lib/cordova/resources.js:150:32)
    at emitNone (events.js:105:13)
    at Response.emit (events.js:207:7)
    at emitNone (events.js:110:20)
    at IncomingMessage.emit (events.js:207:7)
    at endReadableNT (_stream_readable.js:1047:12)
    at _combinedTickCallback (internal/process/next_tick.js:102:11)
    at process._tickCallback (internal/process/next_tick.js:161:9)

STEP2 ionic config set backend pro -gionic loginコマンド

ionic cordova resourcesを実行すると、Ionicアカウントを作ってログインすることを求められる(無料のアカウントでOK)のだが、事前に、ionic config set backend pro -gionic loginコマンドを実行しておかないと、HTTP ステータス410のエラーが帰ってきて、そもそもアカウントの作成ができない。

  • 事前に実行が必要なコマンド
$ ionic config set backend pro -g
[OK] backend set to "pro" in ../../.ionic/config.json!
$ ionic login
Log into your Ionic account
If you don't have one yet, create yours by running: ionic signup

? Email: 

? Password: [hidden]
[OK] You are logged in!
> ionic ssh setup
Looks like you haven't configured your SSH settings yet.

? How would you like to connect to Ionic Pro? Skip for now
[WARN] Skipping for now. You can configure your SSH settings using ionic ssh setup.

ちなみに、、、STEP2を飛ばすと、ionic cordova resourcesコマンド実行時に下記のエラーが出る。

$ ionic cordova resources
[WARN] You need to be logged into your Ionic account in order to run ionic cordova resources.

Log into your Ionic account
If you don't have one yet, create yours by running: ionic signup

? Email: 

? Password: [hidden]
Request: POST https://api.ionic.io/login
Response: 410
Body:
{}

ionic cordova resourcesコマンド実行

上のSTEP1とSTEP2を実行した状態で、ionic cordova resourcesコマンドを打つと、アイコン・スプラッシュスクリーンが自動作成される🍺🍺🍺    

参考サイト

https://forum.ionicframework.com/t/ionic-cordova-resources-not-working/104921/6

ionicframework.com

【Ionic, Cordova】カスタムプラグインを導入した時のiOSビルド手順

はじめに

ionic-plugincordova-pluginなどのプラグインをインストールした後、オリジナル版とは違う修正を加えて、カスタムプラグインを作る事がある。そんなカスタムプラグインを使ったiOSアプリのビルド手順について書いていく。

バージョン情報

今回ビルドするアプリでは、Angular(JavaScriptのWebフレームワーク)ベースの、ハイブリットモバイルアプリ用フレームワークである「Ionic」を使った。

  • Ionic Framework: ionic-angular 3.9.2

  • Node: v8.1.4

  • npm: 5.6.0

  • OS: macOS High Sierra

  • Xcode: Xcode 9.3 Build version 9E145

  • ionic (Ionic CLI) : 3.20.0

  • cordova (Cordova CLI) : 8.0.0

  • Angular: 5.2.9

ビルド手順

カスタムプラグインを作る

まず、インストールしてきたプラグインをベースに、カスタムプラグインを作成する。今回は、cordova-plugin-inappbrowserに独自の改修(初回ログイン以降は、ログイン情報を保持する)を加えて、カスタムプラグインにしている。

f:id:l08084:20180414181340p:plain

  1. 改修を加えたいプラグインをコピーして、custom_pluginsディレクトリ配下に置く

  2. 独自の改修を加える

iOSビルド

  1. node_modulesplatformsplugins配下を全て削除する

  2. ターミナルを開く

  3. $ npm iコマンドでnpmライブラリをインストールする

  4. $ cordova platform add iosコマンドを実行して、プラットフォームとプラグインをインストール&作成

  5. platforms配下を削除

  6. ここで、独自の変更を加えたカスタムプラグインを使いたいので、plugins配下のcordova-plugin-inappbrowserを、custom_plugins配下のcordova-plugin-inappbrowserに入れ替える

  7. 再度$ cordova platform add iosコマンドを実行して、プラットフォームを作成

  8. platforms/ios/[プロジェクト名]/Plugins配下のcordova-plugin-inappbrowserの中身を確認して、カスタムプラグインと同様の中身になっていることを確認する

  9. Xcodeでplatforms/ios/配下の.xcodeprojを開いて、事前にSigningを済ませておく

  10. $ ionic cordova buildd ios --prodコマンドでビルド

  11. Xcodeから、実機もしくはエミュレータ上で動かす

$ npm i
$ cordova platform add ios
# 2度目
$ cordova platform add ios
$ ionic cordova buildd ios --prod

【Xcode】iPhoneでは縦向き固定に、iPadでは横向き固定で表示したい

はじめに

JavaScript(Ionic)を使ってiOSアプリを作成していた時に、「iPadで縦向き(Portrait)表示するとレイアウトが崩れるから、横向き(Landscape)固定にしたい」という要請が発生した。とはいえ、iPhoneでも横向き(Landscape)固定にするのは、使いづらいと思ったので、モバイル端末とタブレット端末でPortraitとLandscapeを切り替える必要があった。

バージョン情報

  • Xcode: 9.3

  • Ionic Framework: ionic-angular 3.9.2

手順

なんかしらのライブラリの導入を検討していたが、Xcodeの設定を変更するだけでできた。

まずXcodeでプロジェクトを選択して、Generalを選択する。そして、Deployment InfoDevicesUniversalに設定する。

これで、iPhone、iPadそれぞれに対して、アプリの表示の向きを設定する事ができる。

iPhoneはPortrait固定に設定

まず、iPhoneから設定していく

f:id:l08084:20180414162435p:plain
iPhoneの設定

Device OrientationチェックボックスでPortrait以外のチェックを外す。

iPadはLandscape固定に設定

続いて、iPadの設定

f:id:l08084:20180414162901p:plain
iPadの設定

Device OrientationチェックボックスでLandscape LeftLandscape Rightのいずれか、もしくは両方を選択する(向きが変わるので、お好みで)。

関連ライブラリ

今回は使用しなかったが、ページごとに表示の向きを固定したりできるライブラリがあるらしい。

github.com

【Angular】HTTPのレスポンスヘッダを取得したい

HTTPのレスポンスヘッダの一つであるETagを取得しようとして四苦八苦した話。

バージョン情報

JavaScriptフレームワークであるAngularを使います

  • Angular: 5.2.9

  • ionic-angular: 3.9.2

ETagとは

https://ja.wikipedia.org/wiki/HTTP_ETag

ETag(エンティティタグ)は、HTTPにおけるレスポンスヘッダの1つである。これは、HTTPにおけるキャッシュの有効性確認の手段の1つであり、ETagを利用してクライアントから条件付きのリクエストを行うことができる。そうすることで、コンテンツが変わらなければレスポンスをすべて返す必要がなくなるので、キャッシュを効率化し、回線帯域を節約できるようになる

本記事は、ETagを取得した段階で終了するので、ETagの中身については、特にツッコミません。

Angularでヘッダーを取得する

Angularのバージョン4.3以降で、HttpClientModuleが導入されたことにより、レスポンスボディだけでなくヘッダーも受け取りたいときは、オプション{ observe: 'response' }を追加する必要があります。また、今回のリクエスト対象が画像のこともあり、{ responseType: 'blob' }も指定しています。

this.http
      .get(imgUrl, { observe: 'response', responseType: 'blob' })
      .subscribe(res => {
        const etag = res.headers.get('ETag');
        console.log(`Etag: ${etag}`);
      });
});

上記のコードでETagが取得できるはずです.........しかしできない。

Etagを取得できない理由: Access-Control-Expose-Headers

ETag(HTTP Headerの一種)を取得できない理由は、フロント(Angular)側でなく、サーバー(API)側にありました。

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers

The Access-Control-Expose-Headers response header indicates which headers can be exposed as part of the response by listing their names.By default, only the 6 simple response headers are exposed If you want clients to be able to access other headers, you have to list them using the Access-Control-Expose-Headers header.

フロント側から、デフォルトでアクセス可能なのは、Cache-Controlなどの6つのHttpヘッダーのみで、ETagだったり他のカスタムヘッダーにアクセスするときは、API側で、Access-Control-Expose-Headers: ETagのように設定してあげる必要があります。

参考サイト

javascript - Get response custom headers for cross-origin request - Stack Overflow

https://github.com/angular/angular/issues/5237

iOS シミュレーター上でだけアプリが動かない!そんな時....

実機(iPhone)だと動くのに、iOSシミュレーターだと動かない!みたいなことがあったので。

バージョン情報

Ionicなどのハイブリットモバイルフレームワークを使用して、JavaScriptでiOSアプリを作った時の話となります

  • ionic-angular 3.9.2

  • Xcode: Version 9.3

  • Angular: 5.2.9

シミュレーターをリセット

シミュレーターだけでエラーが起きるな〜というときは、リセットしてあげるとうまく動くようになるときがある。

f:id:l08084:20180412150020p:plain

シミュレーターの[Hardware] -> [Erase All Content and Settings...] を選択する。

【Xcode】アプリアイコンに関するエラー: Failed to write image data for the app icon set from

f:id:l08084:20180406165258p:plain

Xcodeから、iPhone8 plusに実機ビルドしようとしたタイミングでエラーが発生した。

バージョン情報

  • Xcode: 9.3

  • モバイル端末: iPhone8 plus

  • iOS: 11.1.2

発生したエラー

アプリのアイコン関連と思われる同種のエラーが2件発生した。

エラー1

.../platforms/ios/[アプリ名]/Images.xcassets: Failed to write image data for the app icon set from "AppIcon.appiconset/icon-20@3x.png" to ".../Library/Developer/Xcode/DerivedData/[アプリ名]/Build/Products/Debug-iphoneos/[アプリ名].app/AppIcon20x20@3x.png".

エラー2

.../platforms/ios/[アプリ名]/Images.xcassets: Failed to write image data for the app icon set from "AppIcon.appiconset/icon-50@2x.png" to ".../Library/Developer/Xcode/DerivedData/[アプリ名]/Build/Products/Debug-iphoneos/[アプリ名].app/AppIcon50x50@2x~ipad.png".

エラー文をGoogle翻訳するとこうなる

「AppIcon.appiconset /icon-50@2x.png」から「AppIcon50x50@2x~ipad.png」に設定されたアプリアイコンの画像データの書き込みに失敗しました。

エラーになった画像のオリジナル画像の状態を確認

エラーが発生した画像のオリジナル画像(.../platforms/ios/.../Images.xcassets配下ではなくresoucese配下の方)を確認すると、拡張子がpngの画像ファイルなのに、中身がhtmlファイルになっていたので(前回コミットで画像が消えたことによる現象?)、対象の画像についてだけ、コミットを戻してhtmlからpng形式の画像ファイルに戻したがエラーは消えなかった。

結局問題のファイルを削除した後、手動で新規作成して解決

結局Images.xcassets配下の画像のうち、問題が起きている画像(icon-20@3x、icon-50@2x)を削除 → 別の正常な画像をコピー → サイズを合わせる(20@3xだったら、60 X 60) → ファイル名称も同じにする ことによって解決した。

【Angular】app.module.tsからルーティングのRoute設定を分離する

ファットになりがちなapp.module.tsファイルから、ルーティングの設定を分離したい

バージョン情報

  • Angular: 5.2.9

ルーティングの設定を分離する

Before

app.module.tsの中にルーティングのRoute設定も含まれている

src/app/app.module.ts:

// ...省略
import { RouterModule, Routes } from '@angular/router';

// ...省略

// Route設定
const myRoutes = [
  { path: 'private', component: PrivateComponent },
  { path: '', component: ExchangeListComponent }
];

@NgModule({
  declarations: [AppComponent, ExchangeListComponent, PrivateComponent],
  imports: [
    BrowserModule,
    HttpClientModule,
    NgReduxModule,
    BrowserAnimationsModule,
    MatTableModule,
    // RouterModule
    RouterModule.forRoot(myRoutes)
  ],
  // ...省略
})
export class AppModule {
  // ...省略
}

After

ルーティングのRoute設定については、app.module.tsから切り出して、app.routes.ts内に移動する

src/app/app.module.ts:

// ...省略
import { RouterModule } from '@angular/router';
// 追加
import { myRoutes } from './app.routes';

// ...省略

// Route設定はapp.routes.tsに移動

@NgModule({
  declarations: [AppComponent, ExchangeListComponent, PrivateComponent],
  imports: [
    BrowserModule,
    HttpClientModule,
    NgReduxModule,
    BrowserAnimationsModule,
    MatTableModule,
    // RouterModule
    RouterModule.forRoot(myRoutes)
  ],
  // ...省略
})
export class AppModule {
  // ...省略
}

ルーティングのRoute設定が記載されているファイル

src/app/app.routes.ts:

import { PrivateComponent } from './private/private.component';
import { ExchangeListComponent } from './exchange-list/exchange-list.component';
import { Routes } from '@angular/router';

export const myRoutes: Routes = [
  { path: 'private', component: PrivateComponent },
  { path: '', component: ExchangeListComponent }
];