yamamoWorks

.NET技術を中心に気まぐれに更新していきます

Google Calendar Mobile Gateway 10周年

10年前、どんな携帯電話を使っていましたか?

今やスマートフォンが主流ですが当時はガラケーの全盛期でした。
一方、インターネットでは様々な便利なサービスが出現していましたが基本的にはパソコンのブラウザで利用するもので、携帯電話で利用できるサービスは限られていました。
Googleカレンダーもその1つです。当時は携帯電話でGoogleカレンダーの予定を見ることは出来なかったのです。(後に超使いづらいものが出てきましたが…)

その頃に自宅サーバを立ててWebアプリを作っていた私はGoogleの各種サービスがAPIを公開し始めているのを知り、Googleカレンダーの予定を参照できる携帯電話向けのWebアプリ「Google Calendar Mobile Gateway」を作り2006年7月に公開しました。

version 1 (2006/7)
f:id:yamamoWorks:20160529150344p:plain
version 2 (2007/1)
f:id:yamamoWorks:20160711234358p:plain
version 3 (2009/12)
f:id:yamamoWorks:20160711232426p:plain f:id:yamamoWorks:20160711232424p:plain

 

様々なブログや雑誌でも紹介して頂き、ピーク時(2010年4月)には月間59万PV、1万ユーザに達するなど、多くの方々に利用して頂いたことを大変嬉しく思います。また、フォーラムやブログにも沢山の感謝の言葉や様々な機種での動作報告のコメントを頂き、開発者冥利に尽きる思いでした。

 

少し技術的な面に触れておきますと、当初はASP.NET + SQL Serverの構成で自宅サーバでホストしていました。(現在はASP.NET MVC + Azure SQLをAzure仮想マシンでホスト)

当時の携帯電話のブラウザは1ページの容量が100KBまでで回線速度も遅かったので、ポストバック機構やビューステートで大量のコードをHTMLに吐き出すWebフォームは不向きでした。その為、ASP.NETなんだけどクラシックASP(Active Server Pages)っぽい作りにしていました。後にASP.NET MVCが出てきて「これだ!」と思いversion 3として作り直しました。

GoogleのAPIも初めはAtomベースのGDataでしたが数年前に廃止されJsonベースの新しいAPIに移行されていて時代の流れを感じます。

Google Calendar Mobile Gatewayは皆さんに便利なツールをお届けすると同時に、新しい技術を試す実験場でもありました。

 

スマートフォンの普及に沿うようにGoogle Calendar Mobile Gatewayのアクセス数も年々下がっていて、現在は月間6.5万PV、1500ユーザぐらいです。今のところまだ停止する予定は無いですが、そろそろ役目を終える時期が近づいているのではないかと。

しかし、10年はあっという間ですねぇ…

 

 

Xamarin Formsでアイコンフォントを表示する

UWPではアイコンフォントがSymbol 列挙値として定義されていて簡単にアイコンフォントを表示する事ができますが、残念ながらXamarinにはこのような機能はありません。 f:id:yamamoWorks:20160702164355p:plain:w400

そこでXamarin Formsでも同じアイコンフォントを使えるようにしたいと思います。

準備

Symbol 列挙値で表示されるのはSegoe MDL2 Assetsというフォントで、Windows 10から組み込まれています。 UWPアプリの開発でハンバーガーメニューを表示する際などにもSymbolThemeFontFamilyとして用いられます。 f:id:yamamoWorks:20160702165747p:plain:w400

エクスプローラーで「C:\Windows\Fonts」を開き、フォントファイルを取り出します。他のフォルダにコピーすると分かりますがファイル名は「segmdl2.ttf」です。 f:id:yamamoWorks:20160702221833p:plain:w400


[追記]
Segoe MDL2 Assetsフォントのライセンスについて記載されたドキュメントが見つからない為、ソフトウェアに組み込んで配布可能であるか不明です。このブログ記事ではこれを許可や推奨するものではありません。


Font Awesome フォント

さて、UWPのアイコンフォントをiOSとAndroidでも表示したいというのが発端でしたが、せっかくなので他のアイコンフォントも表示してみます。 今回は「Font Awesome」のアイコンフォントを使います。ダウンロードかGitHubから「FontAwesome.otf」を入手します。

fontawesome.io

フォントの登録

UWP

「FontAwesome.otf」をUWPプロジェクトのどこかに格納しプロパティを以下に設定します。 今回はAssets配下に「Fonts」フォルダを作成して格納します。
 Assets/Fonts/FontAwesome.otf

 ビルドアクション : コンテンツ
 出力ディレクトリにコピー : コピーしない

Android

「segmdl2.ttf」と「FontAwesome.otf」をAndroidプロジェクトの「Assets」に格納しプロパティを以下に設定します。 今回は「Fonts」フォルダを作成して格納します。
 Assets/Fonts/FontAwesome.otf
 Assets/Fonts/segmdl2.ttf

 ビルドアクション : AndroidAsset
 出力ディレクトリにコピー : コピーしない

iOS

「segmdl2.ttf」と「FontAwesome.otf」をiOSプロジェクトの「Resources」に格納しプロパティを以下に設定します。 今回は「Fonts」フォルダを作成して格納します。
 Resources/Fonts/FontAwesome.otf
 Resources/Fonts/segmdl2.ttf

 ビルドアクション : BundleResource
 出力ディレクトリにコピー : コピーしない

次に「Info.plist」にフォント情報を追記します。「Info.plist」を右クリックし「ファイルを開くアプリケーションを選択」から「XMLテキストエディタ」を選択します。
「UIAppFonts」キーを作成してフォントファイルのパスをString配列で記載します。
※Resourcesフォルダからの相対パスです

<plist version="1.0">
  <dict>
    <key>UIAppFonts</key>
    <array>
      <string>Fonts/FontAwesome.otf</string>
      <string>Fonts/segmdl2.ttf</string>
    </array>
       :

※ダブルクリックで開くとPlistEditorが表示されますが、それだとフォント情報の登録ができません。

カスタムレンダラーの作成

AndroidではLabel.FontFamily等でフォント名を指定しても独自に追加したフォントは探してくれません。そこでカスタムレンダラーを作る必要があります。

[assembly: ExportRenderer(typeof(Label), typeof(XFApp1.Droid.CustomFontLabelRenderer))]

namespace XFApp1.Droid
{
    public class CustomFontLabelRenderer : LabelRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
        {
            base.OnElementChanged(e);

            var fontFamily = e.NewElement.FontFamily?.ToLower();
            if (fontFamily != null && (fontFamily.EndsWith(".otf") || fontFamily.EndsWith(".ttf")))
            {
                var lable = (TextView)Control;
                lable.Typeface = Typeface.CreateFromAsset(Forms.Context.Assets, e.NewElement.FontFamily);
            }
        }
    }
}

既存のフォントの指定と追加したフォントの指定とを区別する為、後者はファイル名で指定する仕様としました。

XAMLの記述

LabelのFontFamilyを以下のルールで記載します。

iOS
 既存のフォント : フォント名
 追加したフォント : フォント名

Android
 既存のフォント : フォント名
 追加したフォント : フォントファイルのパス
 ※Assetsフォルダからの相対パス

UWP
 既存のフォント : フォント名
 追加したフォント : フォントファイルのパス#フォント名
 ※プロジェクトフォルダからの相対パス

    <Label Text="FontAwesome" />
    <Label Text="&#xF099; &#xf230; &#xf09b; &#xf179; &#xf17b; &#xf17a;" FontSize="24">
      <Label.FontFamily>
        <OnPlatform x:TypeArguments="x:String"
                    iOS="FontAwesome"
                    Android="Fonts/FontAwesome.otf"
                    WinPhone="Assets/Fonts/FontAwesome.otf#FontAwesome">
        </OnPlatform>
      </Label.FontFamily>
    </Label>

    <Label Text="Segoe MDL2 Assets" />
    <Label Text="&#xE8FA; &#xE8E1; &#xE74E; &#xE907; &#xE909; &#xE8A3;" FontSize="24">
      <Label.FontFamily>
        <OnPlatform x:TypeArguments="x:String"
                    iOS="Segoe MDL2 Assets"
                    Android="Fonts/segmdl2.ttf"
                    WinPhone="Segoe MDL2 Assets">
        </OnPlatform>
      </Label.FontFamily>
    </Label>

※アイコンに対応する文字コードは下記を参照
Font Awesome Cheatsheet
Segoe MDL2 アイコンのガイドライン

これで各プラットフォームでアイコンフォントが表示できるようになりました。 f:id:yamamoWorks:20160702234900p:plain

Xamarin.Forms ListViewのGroupHeaderの高さ調整

いやぁXamarin.Formsは壁が多い奥が深いですね。
今回はListViewをグルーピングした際のレイアウトについてです。

ListViewをグルーピングしてそのヘッダー部分をViewCellでカスタムレイアウトする場合に一筋縄ではいかない事が分かったのでメモしておきます。

下記のようなグルーピングしたListViewを用意します。 ここではGroupHeaderTemplateにTextCellを使用したものとViewCellとLabelを使用したものとを並べてみます。

    <ListView x:Name="listView1" IsGroupingEnabled="True" HasUnevenRows="True">
      <ListView.GroupHeaderTemplate>
        <DataTemplate>
          <TextCell Text="{Binding Key}" />
        </DataTemplate>
      </ListView.GroupHeaderTemplate>
      <ListView.ItemTemplate>
        <DataTemplate>
          <ViewCell>
            <Label Text="{Binding Name}" />
          </ViewCell>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
    ...
    <ListView x:Name="listView2" IsGroupingEnabled="True" HasUnevenRows="True">
      <ListView.GroupHeaderTemplate>
        <DataTemplate>
          <ViewCell>
            <Label Text="{Binding Key}" />
          </ViewCell>
        </DataTemplate>
      </ListView.GroupHeaderTemplate>
      <ListView.ItemTemplate>
        <DataTemplate>
          <ViewCell>
              <Label Text="{Binding Name}" />
          </ViewCell>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>

以下、各プラットフォームでの表示と対応方法です。

Android

特に違和感はありません。
f:id:yamamoWorks:20160619184825p:plain:w350

iOS

ViewCellを使用している方の高さが残念ですが、これはViewCellのHeightを指定すれば解決します。

f:id:yamamoWorks:20160619191743j:plain:w300 f:id:yamamoWorks:20160619191755p:plain:w300
修正前   修正後
      <ListView.GroupHeaderTemplate>
        <DataTemplate>
          <ViewCell Height="22">
            <Label Text="{Binding Key}" />
          </ViewCell>
        </DataTemplate>
      </ListView.GroupHeaderTemplate>

UWP

こちらもViewCellを使用している方の高さが残念です。 そしてUWPではViewCellのHeightを指定しても解決しません。 UWPプロジェクトのApp.xamlファイルでListViewHeaderItemの既定のスタイルを上書きする必要があります。

f:id:yamamoWorks:20160619202144j:plain:w350 f:id:yamamoWorks:20160619193155p:plain:w350
修正前   修正後

App.xaml

    <Application.Resources>
        <x:Double x:Key="ListViewHeaderItemMinHeight">22</x:Double>
    </Application.Resources>

解説

これはXamarin.Forms.Platform.UWP.ListViewRendererがUWPのListViewを内部で生成する際にGroupStyleSelectorの値にResource.xamlで"ListViewGroupSelector"として定義されているXamarin.Forms.Platform.UWP.ListViewGroupStyleSelectorを設定していて、この ListViewGroupStyleSelectorがResource.xamlで"ListViewGroup"として定義されているGroupStyleを返すのですが、その中でHeaderContainerStyleとしてListViewHeaderItemが指定されており、UWPでその既定のスタイルがMinHeightが44になっている事が原因です。

ListViewHeaderItem スタイルとテンプレート - Windows app development

通常はXamarin.Formsで対応していないプラットフォーム側のパラメーターの調整はCustom Rendererでも作らない限り手が出ませんが、UWPのスタイルテンプレートの仕組みを活用すれば何とかなる場合もある事が分かりました。

NavigationPageの前ページタイトルを消す

Xamarin.FormsのNavigationPageでページ遷移した際、iOSでは既定で戻るボタン「<」と前ページのタイトルが表示されます。

f:id:yamamoWorks:20160612233239p:plain:w250 → f:id:yamamoWorks:20160612233252p:plain:w250

前ページのタイトルを非表示にする方法を調べたところ、画面遷移処理の際にNavigationPage.SetBackButtonTitle()を呼べばいいと分かったのですが

NavigationPage.SetBackButtonTitle(this, "");
await Navigation.PushAsync(new LoginPage());

Prism.Formsを使っている場合は画面遷移の処理が異なるので更に調べていたところ、廃止されているINavigationPageProviderインターフェイスを使った方法が出てきて、じゃあINavigationServiceインターフェイスをゴニョゴニョするのか?とか迷走しておりましたが、単純に前ページのコンストラクタでNavigationPage.SetBackButtonTitle()を呼べばよいと分かりました。

public MainPage()
{
    InitializeComponent();
    NavigationPage.SetBackButtonTitle(this, "");
}

f:id:yamamoWorks:20160612233239p:plain:w250 → f:id:yamamoWorks:20160612235633p:plain:w250

これでスッキリしました。

MacBookを持ってないけど外出先でXamarin.iOSをビルドしたい

最近、Xamarinを触り始めていてイベントや勉強会に参加する機会があるのですが、私が持っているのはMacBookではなくSurface Pro 4なので、その場でiOSのプロジェクトはビルド出来ないのですよ。(する必要があるのかはさておき)

そこで自宅で使用しているMac miniに外出先から繋げてリモートビルドが出来るようにしたのでメモしておきます。
macincloud等のクラウドサービスもありますが、今回は自前で構築します。

方法は簡単、自宅にVPNサーバを導入するだけです。

今回はSoftEtherを利用しました。 ウチはマンションで独自のプロバイダーが入っているので直接VPNを公開できないのですが、VPN Azureという機能で間接的にVPN接続を行う方式で実現しています。

1つだけ注意点、ビルドホストとなるMacにSoftEtherをインストールしたいところですが下記の制限事項があるので、同一LAN内の別のWindowsマシンにSoftEtherをインストールしています。
VPNFAQ032. Linux で VPN Server のローカルブリッジを作成するとホストコンピュータ自身と通信できない - SoftEther VPN プロジェクト

Surface側はWindows標準のVPN接続の設定でSSTPを選べばOK。

Xamarin で iOS Simulator for Windowsも動かせるようにしておけば、MacBookが無くても外出先からXamarin.iOS開発が可能に! (Azure経由のVPNなので動きはモッサリしてますが…) nuits.hatenadiary.jp