Delphi 10.1 Berlin Update2でiOS 10用にユーザーデータのアクセス目的を記述するには

iOS 10から、カメラなどを使用するときには、info.plistに特定のキーと使用目的を記述するようになりました。

Delphi 10.1 Berlin Update2では、プロジェクトオプションのバージョン情報画面で、設定できます。

delphi101belrin_infoplist

位置情報へのアクセス (常に)

  • キー
    NSLocationAlwaysUsageDescription


  • アプリが常にユーザーの位置情報にアクセスする理由

位置情報へのアクセス (使用中のみ)

  • キー
    NSLocationWhenInUseUsageDescription


  • アプリがフォアグラウンドで実行されているときなど、アプリがユーザーの位置情報にアクセスする理由

連絡先へのアクセス

  • キー
    NSContactsUsageDescription


  • アプリがユーザーの連絡先にアクセスする理由

フォトライブラリへのアクセス

  • キー
    NSPhotoLibraryUsageDescription


  • アプリがユーザーの写真ライブラリにアクセスする理由

カメラへのアクセス

  • キー
    NSCameraUsageDescription


  • アプリがデバイスのカメラにアクセスする理由

Delphiのフォーマッタ(コードの整形)でDataMoudleが壊れる

TDataMoudleにはClassGroupプロパティがあります。 このプロパティで、データモジュールがVCL用かFMX用かフレームワークに依存しないかを設定します。

たとえばTTimerコンポーネントはVCL用のVcl.ExtCtrls.TTimerとFMX用のFMX.Types.TTimerがあります。 使用するコンポーネントとClassGroupで設定したフレームワークを一致させる必要があります。

formatter01

オブジェクトインスペクタでClassGroupプロパティを設定すると、ユニットファイルには次のような指令が追加されます。

ClassGroupがFMX.Controls.TControlの場合:

{%CLASSGROUP 'FMX.Controls.TControl'}

フォーマッタのコメントスペースの設定の「{ および (* コメントスペースの設定」は初期値が「内側と外側」になっています。 この設定でコードを整形すると、ClassGroupプロパティによって追加された指令が次のようになります。

{ %CLASSGROUP 'FMX.Controls.TControl' }

「{」「}」の間に空白が追加されました。

このように空白が追加されると、ClassGroupプロパティの値は未設定の状態になります。

ClassGroupプロパティの値が設定されていない状態になると、コンポーネントとフレームワークに不一致が生じます。 その結果、IDEでデータモジュールを読み込むときにエラーが発生します。

エラーが発生した場合は、ClassGroupプロパティの指令を修正すれば、元の状態に戻ります。

Pythonでテキストファイルの改行コードをCRLFからLFに変換するには

Pythonでテキストファイルの改行コードをCRLFからLFに変換したい。

文字列を改行コードを指定してテキストファイルに保存する方法が見つからなかった。

バイト列にして保存することで実現できた。

環境

  • Windows 10
  • Python 2.5.1

手順

(1) 改行コードがCRLF(\r\n)になっているテキストファイルを読み込む。

with open('test1.txt', 'r', encoding='utf-8') as a_file:
  txt = a_file.read()

(2) 改行コードCR(\r)を削除する。

txt = txt.replace('\r', '')

(3) ファイルをバイナリモードで開く。

with open('test2.txt', 'wb') as a_file:

(4) ファイルに、文字列をUTF-8のバイト列にして保存します。

a_file.write(txt.encode('utf-8'))

ソースコード

#! python3

# テキストファイルから文字列を読み込む
with open('test1.txt', 'r', encoding='utf-8') as a_file:
  txt = a_file.read()

# 読み込んだ文字列のCR(\r)を削除する。
txt = txt.replace('\r', '')

# ファイルをバイナリモードで開く
with open('test2.txt', 'wb') as a_file:
  # 文字列をバイト列にして保存する
  a_file.write(txt.encode('utf-8'))

iOSアプリで「SecTrustEvaluate [leaf ValidLeaf]」

開発しているiOSアプリが、Webサーバーにアクセスできなくなった。

ログを見ると次のメッセージを見つけた。

SecTrustEvaluate [leaf ValidLeaf]

ホーム画面で「設定」→「一般」→「日付と時刻」→「自動設定を(オン)に切替」で解決した。

古いバージョンのCocoaPodsをインストールするには

CocoaPodsはバージョン1.0.0からpodfileの書き方が変更された。
プロジェクトで使用しているpodfileが古いバージョン場合、古いCocoaPodsが必要になることもある。

インストールされているCocoaPodsのバージョンを確認する。

pod --version

CocoaPodsをアンインストールする。

sudo gem uninstall cocoapods

バージョンを指定して、CocoaPodsをインストールする。 # ここではバージョン0.39.0をインストールする。

sudo gem install -v 0.39.0 cocoapods

HTMLのCanvasのwidth/height属性とCSSの設定の違いについて

幅と高さを指定したcanvasに円を描いた。

<canvas id="canvas1" width="200" height="200"></canvas>
<canvas id="canvas2" style="width: 200px; height: 200px;"></canvas>
<script>
    function drawArc(id) {
        var canvas = document.getElementById(id);
        var ctx = canvas.getContext('2d');
        ctx.beginPath();
        ctx.arc(100, 100, 90, 0, Math.PI * 2, true);
        ctx.stroke();
    }
    drawArc('canvas1');
    drawArc('canvas2');
</script>

同じ円が描かれると思っていたが、
実行すると、異なる2つ円が描かれた。

これはどういうことだろうか。

canvasは、width属性とheigh属性で指定された描画領域に描画する。
描画領域に描かれた画像は、CSSの指定に合わせて拡大・縮小されて表示される。

canvasは、width属性とheigh属性が指定されなかった場合、幅300ピクセル、高さ 150ピクセルの要素として初期化される。
canvas2は、幅300x高さ150ピクセルの描画領域に円を描き、styleで指定した幅200x高さ200ピクセルに画像のサイズを変更して表示しているため、楕円が表示されたようだ。

canvasのグリッドについて、MDNの次のページが参考になった。

Androidアプリでネイティブコントロールをフォームに配置するには

Androidアプリでネイティブコントロールをフォームに配置するには

Delphi 10.1 BerlinのFireMonkeyアプリケーションでAndroidのネイティブコントロールをフォームに配置する方法です。

コントロールを配置する

DelphiのフォームにAndroidのネイティブコントロールを配置するには、JNativeLayoutを使用します。

uses Androidapi.JNI.Embarcadero;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { private 宣言 }
    FNativeLayout: JNativeLayout;
  public
    { public 宣言 }
  end;

Androidのネイティブコントロールを使用するときは、 CallInUIThread関数またはCallInUIThreadAndWaitFinishing関数を使用して、 AndroidのUIスレッドで行います。

CallInUIThread関数は非同期で処理を行います。
CallInUIThreadAndWaitFinishing関数は処理が完了するまでブロックします。

uses FMX.Helpers.Android;

CallInUIThreadAndWaitFinishing(
  procedure
  begin
    // ネイティブコントロールを使用する
  end);

JNativeLayoutのインスタンスを作成します。

uses Androidapi.Helpers, FMX.Platform.Android;

FNativeLayout := TJNativeLayout.JavaClass.Init(
  TAndroidHelper.Activity,
  MainActivity.getWindow.getDecorView.getWindowToken);

ネイティブコントロールを作成し、JNativeLayoutに登録します。 今回のサンプルではEditTextを配置します。

uses Androidapi.JNI.Widget;

EditText := TJEditText.JavaClass.Init(TAndroidHelper.Activity);
FNativeLayout.setControl(EditText);

JNativeLayoutに登録できるコントロールは一つだけです。
また、nilを登録すると、登録されていたコントロールが削除されます。

コントロールの位置と大きさを設定します。
この設定は、JNativeLayoutにコントロールを登録した後に行います。

  FNativeLayout.setPosition(50, 300);
  FNativeLayout.setSize(400, 200);

コントロールを非表示にする

事前にウィンドウのサイズを取得しておきます。

type
  TForm1 = class(TForm)
  …
  private
    { private 宣言 }
    FRealBounds: TRect;
    procedure CalcRealBorder;
  …
  end;

procedure TForm1.CalcRealBorder;
var
  NativeWin: JWindow;
  ContentRect: JRect;
begin
  NativeWin := TAndroidHelper.Activity.getWindow;
  if NativeWin <> nil then
  begin
    ContentRect := TJRect.Create;
    NativeWin.getDecorView.getDrawingRect(ContentRect);
    FRealBounds := Rect(ContentRect.left, ContentRect.top, ContentRect.Right,
      ContentRect.bottom);
  end
  else
    FRealBounds := TRect.Empty;
end;

コントロールを非表示にする処理は、UIスレッドで行います。

procedure TForm1.ButtonHideClick(Sender: TObject);
begin
  CallInUIThread(
    procedure
    begin
      if FEditText.getVisibility <> TJView.JavaClass.INVISIBLE then
      begin
        FEditText.setVisibility(TJView.JavaClass.INVISIBLE);
        FNativeLayout.setPosition(
          FRealBounds.Right * 2,
          FRealBounds.Height * 2);
      end;
    end);
end;

コントロールを表示する

非表示にしたコントロールを再表示します。

procedure TForm1.ButtonShowClick(Sender: TObject);
begin
  CallInUIThread(
    procedure
    begin
      FNativeLayout.setPosition(50, 300);
      FNativeLayout.setSize(400, 200);

      if FEditText.getVisibility <> TJView.JavaClass.VISIBLE then
      begin
        FEditText.setVisibility(TJView.JavaClass.VISIBLE);
      end;
    end);
end;

サンプルアプリケーション

Createボタンを押すとコントロールを作成し、Hideボタンで非表示にし、Showボタンで再表示します。

device-2016-10-15-193530

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes,
  System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics,
  FMX.Dialogs, Androidapi.JNI.Embarcadero, FMX.Controls.Presentation,
  FMX.StdCtrls, Androidapi.JNI.Widget;

type
  TForm1 = class(TForm)
    ButtonCreate: TButton;
    ButtonShow: TButton;
    ButtonHide: TButton;
    procedure ButtonCreateClick(Sender: TObject);
    procedure ButtonShowClick(Sender: TObject);
    procedure ButtonHideClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { private 宣言 }
    FEditText: JEditText;
    FNativeLayout: JNativeLayout;
    FRealBounds: TRect;
    procedure CalcRealBorder;
  public
    { public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

uses Androidapi.Helpers, FMX.Platform.Android, FMX.Helpers.Android,
  Androidapi.JNI.GraphicsContentViewText;

procedure TForm1.ButtonCreateClick(Sender: TObject);
begin
  CallInUIThreadAndWaitFinishing(
    procedure
    begin
      FNativeLayout := TJNativeLayout.JavaClass.Init(TAndroidHelper.Activity,
        MainActivity.getWindow.getDecorView.getWindowToken);

      FEditText := TJEditText.JavaClass.Init(TAndroidHelper.Activity);
      FEditText.setText(StrToJCharSequence('テスト'),
        TJTextView_BufferType.JavaClass.NORMAL);

      FNativeLayout.setControl(FEditText);
      FNativeLayout.setPosition(50, 300);
      FNativeLayout.setSize(400, 200);
    end);
end;

procedure TForm1.ButtonHideClick(Sender: TObject);
begin
  CallInUIThread(
    procedure
    begin
      if FEditText.getVisibility <> TJView.JavaClass.INVISIBLE then
      begin
        FEditText.setVisibility(TJView.JavaClass.INVISIBLE);
        FNativeLayout.setPosition(FRealBounds.Right * 2,
          FRealBounds.Height * 2);
      end;
    end);
end;

procedure TForm1.ButtonShowClick(Sender: TObject);
begin
  CallInUIThread(
    procedure
    begin
      FNativeLayout.setPosition(50, 300);
      FNativeLayout.setSize(400, 200);

      if FEditText.getVisibility <> TJView.JavaClass.VISIBLE then
      begin
        FEditText.setVisibility(TJView.JavaClass.VISIBLE);
      end;
    end);
end;

procedure TForm1.CalcRealBorder;
var
  NativeWin: JWindow;
  ContentRect: JRect;
begin
  NativeWin := TAndroidHelper.Activity.getWindow;
  if NativeWin <> nil then
  begin
    ContentRect := TJRect.Create;
    NativeWin.getDecorView.getDrawingRect(ContentRect);
    FRealBounds := Rect(ContentRect.left, ContentRect.top, ContentRect.Right,
      ContentRect.bottom);
  end
  else
    FRealBounds := TRect.Empty;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  CalcRealBorder;
end;

end.

ソースコードはGitHubで取得できます。

flowtypeに入門してみる

Flowとは

flowは、JavaScriptのコードに対して静的な型チェックを行います。
プログラムのバグを早い段階で発見できます。

インストール

プロジェクトのフォルダーに移動して「npm init」を実行します。

npm init

「package.json」ファイルが作成されました。

flow本体とコマンドラインインターフェースをインストールします。

npm install --save flowtype flow-bin

package.jsonの「scripts」に「”flow”: “flow”」を追加します。

{
    "name": "flowtest",
    "version": "1.0.0",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "flow": "flow"
    },
    "author": "",
    "license": "ISC",
    "dependencies": {
        "flow-bin": "^0.32.0",
        "flowtype": "^1.0.0"
    },
    "description": ""
}

「flow init」を実行します。

rpm run-script flow init

「.flowconfig」ファイルが作成されました。

型をチェックする

「flow check」で型をチェックします。

rpm run-script flow check

実行結果は次のようになりました。

Found 0 errors

チェックするソースコードがないので、エラーはありませんでした。

「index.js」ファイルを作成する

「index.js」ファイルを作成します。 

// @flow
function add(num1, num2) {
    return num1 + num2;
}
var x = add(3, '0');
console.log(x);

型チェックします。

rpm run-script flow check

実行結果は次のようになりました。

Found 0 errors

エラーにはなりませんでした。

関数に型を指定する

関数の引数と戻り値の型を指定します。

// @flow
// 引数と戻り値の型を指定する
function add(num1: number, num2: number): number {
    return num1 + num2;
}
var x = add(3, '0');
console.log(x);

型チェックします。

rpm run-script flow check

実行結果は次のようになりました。

index.js:6
  6: var x = add(3, '0');
             ^^^^^^^^^^^ function call
  6: var x = add(3, '0');
                    ^^^ string. This type is incompatible with
  3: function add(num1: number, num2: number): number {
                                      ^^^^^^ number


Found 1 error

エラーを検出しました。
number型の引数にstringを渡していることがエラーになっています。

エラーになっている箇所を修正します。
stringではなく、numberを渡します。

// @flow

function add(num1: number, num2: number): number {
    return num1 + num2;
}
var x = add(3, 2); // 引数を修正する
console.log(x);

型チェックします。

rpm run-script flow check

実行結果は次のようになりました。

Found 0 errors

ソースコードから型を削除する

ソースコードから型を削除するには、「flow-remove-types」を使用します。

「flow-remove-types」をインストールします。

npm install --save flow-remove-types

index.jsから型を削除したoutput.jsを作成します。

node node_modules\flow-remove-types\flow-remove-types index.js > output.js

output.jsが作成されました。

//      

function add(num1        , num2        )         {
    return num1 + num2;
}
var x = add(3, 2);
console.log(x);

Flow Comments形式

ソースコードに型を記述する形式に、Flow Comments形式があります。

型をJavaScriptのコメントとして記述するため、ソースコードはそのままJavaScriptとして実行できます。

// @flow

function add(num1/*: number */, num2/*: number */)/*: number */ {
    return num1 + num2;
}

最後に

既存のソースコードを壊すことなく、型チェックの仕組みを追加できるのはいいですね。

webpackでエラー「SyntaxError: missing ) after argument list」

webpackでエラー「SyntaxError: missing ) after argument list」

webpackを実行するとエラーになる。

>node node_modules\.bin\webpack app.js bundle.js
c:\Users\~\node_modules\.bin\webpack:2
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
          ^^^^^^^

SyntaxError: missing ) after argument list
    at exports.runInThisContext (vm.js:53:16)
    at Module._compile (module.js:373:25)
    at Object.Module._extensions..js (module.js:416:10)
    at Module.load (module.js:343:32)
    at Function.Module._load (module.js:300:12)
    at Function.Module.runMain (module.js:441:10)
    at startup (node.js:139:18)
    at node.js:974:3

webpackのパスを「node_modules.bin\webpack」から「node_modules\webpack\bin\webpack」に変更すると、正しく実行できるようになった。

>node node_modules\webpack\bin\webpack app.js bundle.js
Hash: b5d1e561b44e32934914
Version: webpack 1.13.2
Time: 59ms
    Asset     Size  Chunks             Chunk Names
bundle.js  1.49 kB       0  [emitted]  main
   [0] ./app.js 98 bytes {0} [built]

Google Feed APIの代替手段と設定方法

Google Feed APIがもうすぐ終了します。

このサイトでは、Google Feed APIを使用してRSSから更新情報を取得し、表示しているページがありました。 Google Feed APIの代わりとなるサービスを調べ、乗り換えました。

乗り換え先として選んだサービスと、その設定方法を紹介します。

乗り換え先の条件

乗り換え先は次の条件を満たすものを探しました。

  • 無料で使用できること。
  • ブラウザだけで動作すること。サーバーサイドのプログラムは不要であること。
  • 一昔前のブラウザでも動作すること。最新技術に依存していないこと。

Yahoo!Japanの爆速YQL

選んだのが「Yahoo!Japanの爆速YQL」というサービスです。 上記の条件をすべて満たしており、使い方も簡単でした。

爆速YQLについては、次のページをご覧ください。

爆速YQLの使い方

基本となるひな形は次のようになります。

<html>
<body>

<script
  src="http://i.yimg.jp/images/yjdn/js/bakusoku-yql-v1-min.js"
  data-url="[RSSのURL]"
  data-p-[パラメータ名]="[値]">
  [HTMLのテンプレート]
</script>

</body>
</html>

scriptタグは、更新情報を表示したい場所に置きます。

URLを設定する

[RSSのURL]の部分には、実際のRSSのURLを設定します。

RSSのURLにパラメータが含まれている場合は、scriptタグの属性に「data-p-[パラメータ名]=”[値]”」を追加します。
パラメータがない場合は「data-p-[パラメータ名]」は不要です。

たとえば「山本隆の開発日誌」のRSSは、URLが「http://www.gesource.jp/weblog/?feed=rss2」です。
この場合、パラメータ名が「feed」、値が「rss2」となり、scriptタグは次のようになります。

<script
  src="http://i.yimg.jp/images/yjdn/js/bakusoku-yql-v1-min.js"
  data-url="http://www.gesource.jp/weblog/"
  data-p-feed="rss2">
  [HTMLのテンプレート]
</script>

デバッグモードで受信したデータを表示する

URLを正しく設定できたか確認します。

HTMLのテンプレートが空の時は、デバッグモードで動作し、取得されたデータが表示されます。

<script
  src="http://i.yimg.jp/images/yjdn/js/bakusoku-yql-v1-min.js"
  data-url="http://www.gesource.jp/weblog/"
  data-p-feed="rss2"></script>

scriptタグの位置に取得したデータが表示されると成功です。

{
"query": {
    "count": 1,
    …
  }
}

HTMLのテンプレートを設定する

次に、HTMLのテンプレートを設定します。

HTMLのテンプレートには「mustache」というライブラリが使用されているようです。
ですが、mustacheライブラリの詳細を知る必要はありません。
テンプレートで使用する機能は簡単なものです。

デバッグモードで確認したデータで表示したいデータを確認します。
「query.results.rss.channel.item」が記事の情報になっていました。
この部分を繰り返し表示します。

<script
  src="http://i.yimg.jp/images/yjdn/js/bakusoku-yql-v1-min.js"
  data-url="http://www.gesource.jp/weblog/"
  data-p-feed="rss2">
  <ul>
  {{#query.results.rss.channel.item}}
    <li>
      {{pubDate}}<br>
      <a href="{{link}}">{{title}}</a>
    </li>
  {{/query.results.rss.channel.item}}
  </ul>
</script>

テンプレートの中のHTMLタグはそのまま表示されます。
「{{」と「}}で囲んだ部分がjavaScriptで処理されるコードになります。

  {{#query.results.rss.channel.item}}
  …
  {{/query.results.rss.channel.item}}

と書くと、「query.results.rss.channel.item」のデータが繰り返し表示されます。

繰り返しの部分の中で「{{title}}」と書くと、「query.results.rss.channel.item」の「title」の値が表示されます。

「{{#~}}」「{{/~}}」で繰り返し、「{{~}}」で値の表示、これだけわかっていれば使えます。

テンプレートについて詳しく知りたい方は、次のページをご覧ください。

日付の書式を変更する

日付(pubDate)の値が「Thu, 15 Sep 2016 23:47:18 +0000」という形式になっています。
この形式を「2016年9月16日」と表示するように修正します。

scriptタグの属性に「data-filter=”filterRSS”」を追加します。
「data-filter=”関数名”」属性を追加すると、JavaScriptの関数が呼ばれようになります。
関数で受け取ったデータを修正できます。

<script
  src="http://i.yimg.jp/images/yjdn/js/bakusoku-yql-v1-min.js"
  data-url="http://www.gesource.jp/weblog/"
  data-p-feed="rss2"
  data-filter="filterRSS">
  <ul>
  {{#query.results.rss.channel.item}}
    <li>
      {{pubDate}}<br>
      <a href="{{link}}">{{title}}</a>
    </li>
  {{/query.results.rss.channel.item}}
  </ul>
</script>

「filterRSS」関数を作成します。

<script>
function filterRSS(data) {
    for (var i = 0; i < data.query.results.rss.channel.item.length; ++i) {
        var date = new Date(data.query.results.rss.channel.item[i].pubDate);
        data.query.results.rss.channel.item[i].pubDate = date.getFullYear() + '年' + (date.getMonth() + 1) + '月' + date.getDate() + '日';
    }
    return data;
}
</script>

JavaScriptの「var date = new Date(文字列)」で日付の文字列からオブジェクトを作成し、puDateの値を上書きしています。

テンプレートの機能を活用すれば、もっと簡単にできるかもしれませんが、今回はこのような方法で実現しました。

最後に

サイトのトップページでは、この方法でブログの更新情報を表示しています。

参考になれば幸いです。