Microsoft 系のあれこれ

港区の SIer で よくわからんことをしている SE みたいな人です。Xamarin 中心です。

Xamarin.FormsでiOSのスプラッシュの背景色が意図した色にならない

あるアプリでテーマカラーを以下のような色にしたかったのですが、スプラッシュ画面が意図した色にならなかったため、その対応方法です。
「Xamarin.Formsで」と記載されてるのは同じような問題が Mac で普通に iOS のアプリを作っても発生するのかわからなかった(知らない)ので、保険として。
f:id:ShunsukeKawai:20180926173433p:plain

iOS でのスプラッシュには Storyboard を使用しています。

まとめ

  • Visual Studio で Storyboard は編集しない
  • ColorSpace は sRGB

現象再現手順

WindowsVisual Studio で Storyboard の背景色を変更します。
f:id:ShunsukeKawai:20180926181720p:plain

アプリを起動します。
↓スプラッシュ
f:id:ShunsukeKawai:20180926182504p:plain:w400

はい、その次の画面で同じ色を背景にしたログイン画面が出てくるのですが、比べてみましょう。
↓ログイン画面
f:id:ShunsukeKawai:20180926182646p:plain:w400

違いますね。

色々調べてみるとXamarin のリポジトリにも Issue があがってました。
Xamarin の問題じゃないぞ、とクローズされてますが、解決方法が書いてありました。
iOS Storyboard Launch Screen color issue · Issue #1750 · xamarin/Xamarin.Forms · GitHub

This particular issue does look like a mismatched colorspace. We don't currently expose an easy way to alter the colourspace, but it is on the roadmap.

For now can you can open your storyboard in Xcode and access the colorspace selector like this: http://recordit.co/JAVdR1FHjh . If you want things to match you will have to use the same RGB values and the same colorspace for both controls. There is some more information here about handling colorspaces: https://developer.apple.com/ios/human-interface-guidelines/visual-design/color/

colorspace が違うから、と書いてあるようです。
ということで対応します。

対応方法

先ほど編集した Storyboard をエディターで開くと色を定義している箇所が以下の様になっています。

<color key="backgroundColor" red="0.84313725490196079" green="0.23137254901960785" blue="0.00784313725490196" alpha="1" colorSpace="calibratedRGB"/>

ここの colorSpace="calibratedRGB" が悪いヤツみたいです。

MacXcode で対象の Storyboard を開き、色を変更するウィンドウの歯車マークを選択して sRGB ~~ を選択して、再度正しい色の値を設定します。
f:id:ShunsukeKawai:20180926185849p:plain

編集した Storyboard は以下の様になっています。

<color key="backgroundColor" red="0.84313725490196079" green="0.23137254901960785" blue="0.0078431372549019607" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>

colorSpace="custom" customColorSpace="sRGB"の部分ですね。

この部分の変更を元のプロジェクトにしてやって再度アプリを起動します。
f:id:ShunsukeKawai:20180926191942p:plain:w400

ちゃんと意図した色になりました!!

この Storyboard を再度 Visual Studio の Interface Builder で編集するとcolorSpace="calibratedRGB"に戻ります(汗
そのため、今後は Storyboard を Visual Studio で編集しないようにしましょうっとw

補足

  • Visual Studio for Mac でも同じ現象が再現しました。
  • GenericRGB はもう使うなって Apple は言ってる?

kCGColorSpaceGenericRGB - Core Graphics | Apple Developer Documentation

  • ネイティブ開発でも同じなのかな?

InterfaceBuilderで色を設定するときはcolorSpaceに気をつけよう

GitHubの組織へのメンバー変更をMicrosoft Teamsに通知する(by Microsoft Flow)

ちょっと手順が面倒だったのでまとめておきます。
GitHub の Organization で会社のソース管理諸々をしているのですが、メンバーの変更が流動的だったりで変更の内容を Microsoft Teams に通知したかったのです。
Microsoft Flow を使って連携します。
今回実現したいことは

  • GitHub の Organization にメンバーが招待された、削除された時に通知がしたい

です。

目次

Microsoft Teams にコネクタを追加する

まずは通知先の Teams のチャンネルにコネクタを追加します。
f:id:ShunsukeKawai:20180719191506p:plain
表示されたコネクタ一覧から Incoming Webhook を選択します。
f:id:ShunsukeKawai:20180719191648p:plain
適当な名前と画像を設定して作成すると URL が生成されるので、それを控えておきます。
f:id:ShunsukeKawai:20180719191954p:plain

Microsoft Flow にトリガーを追加する

トリガーの作成

今回はマッシュアップ的なサービスである Microsoft Flow を利用して連携します。
プロセスとタスクの自動化 | Microsoft Flow
様々なテンプレートが用意されていますが、新規作成します。(以下の URLから)
https://japan.flow.microsoft.com/manage/flows/new
トリガーとなるコネクタ一覧から「要求」というのを選択します。(もうちょっと名前どうにかならんのかな…)
f:id:ShunsukeKawai:20180719193024p:plain
表示されたトリガーから「要求 - HTTP 要求の受信時」というのを選択します。(もうちょっと名前どうにかならんのかな…2)
f:id:ShunsukeKawai:20180719193614p:plain
次の画面で「サンプルのペイロードを使用してスキーマを生成する」を選択します。
f:id:ShunsukeKawai:20180719194732p:plain
その画面のまま次の手順に進みます。

GitHub の Webhooks のペイロードからスキーマを設定

GitHub の Organization 関連の Webhook の内容は以下の GitHub 公式ドキュメントを参考にします。
https://developer.github.com/v3/activity/events/types/#organizationevent
そこの Webhook payload example に記載されてる JSON を丸ごとコピーします。
https://developer.github.com/v3/activity/events/types/#webhook-payload-example-17
この JSON からスキーマが作成され、後で作成するアクションに対して簡単に埋め込めるようになります。
※ 上記公式サンプルは action が member_invited の場合ではないので一旦コピーした JSON をなにかエディターに貼り付けて action と membership の間に以下の invitation 部分を追加します。

  "action": "member_added",

    "invitation": {
        "id": 999999,
        "node_id": "XXXXXXXXXXXXXXXXXXXXXXXXXXX",
        "login": null,
        "email": "XXXXX@XXXX.co.jp",
        "role": "direct_member",
        "created_at": 9999999999,
        "inviter": {
            "login": "shunsuke-kawai",
            "id": 6369070,
            "node_id": "MDQ6VXNlcjYzNjkwNzA=",
            "avatar_url": "https://avatars2.githubusercontent.com/u/6369070?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/shunsuke-kawai",
            "html_url": "https://github.com/shunsuke-kawai",
            "followers_url": "https://api.github.com/users/shunsuke-kawai/followers",
            "following_url": "https://api.github.com/users/shunsuke-kawai/following{/other_user}",
            "gists_url": "https://api.github.com/users/shunsuke-kawai/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/shunsuke-kawai/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/shunsuke-kawai/subscriptions",
            "organizations_url": "https://api.github.com/users/shunsuke-kawai/orgs",
            "repos_url": "https://api.github.com/users/shunsuke-kawai/repos",
            "events_url": "https://api.github.com/users/shunsuke-kawai/events{/privacy}",
            "received_events_url": "https://api.github.com/users/shunsuke-kawai/received_events",
            "type": "User",
            "site_admin": false
        }
    },

   "membership": {
    …

編集した JSON を先ほどの Flow のページに貼り付けるとスキーマを作成してくれます。(左のメッセージは「わかりました」で OK です。)
f:id:ShunsukeKawai:20180719205121p:plain

Microsoft Flow に Teams に通知するアクションを追加する

アクションの作成

先ほど作成したトリガーの下に「新しいステップ」というのがあるので選択して「条件の追加」を選択します。
f:id:ShunsukeKawai:20180719205240p:plain
「値の選択」にフォーカスを当てると以下の画面が出てきます。先ほどの JSON から作成してくれたスキーマです。
f:id:ShunsukeKawai:20180719205351p:plain
今回は招待されたらという条件を追加したいので「action」を選択して、GitHub 公式ドキュメントにあった値である「member_invited」を設定します。
f:id:ShunsukeKawai:20180719205611p:plain
続いてその条件が True だった時に行うアクションを設定します。
f:id:ShunsukeKawai:20180719205628p:plain
アクションの追加から「HTTP」を選択します。
f:id:ShunsukeKawai:20180719205719p:plain
表示されたアクションから「HTTP - HTTP」を選択します。
方法に「POST」URI に Teams でコネクタを作成した時に生成された URL を設定します。
f:id:ShunsukeKawai:20180719210047p:plain

本文の設定

本文の設定内容については以下の記事を参考にさせていただきました。
Microsoft Flow を使って Microsoft Teams へ投稿する(続編) | idea.toString();
以下のような JSON に編集しました。後程設定するので value が空の箇所があります。

{
    "@@type": "MessageCard",
    "@@context": "http://schema.org/extensions",
    "themeColor": "ff4fbc",
    "summary": "概要",
    "sections": [
      {
        "activityTitle": "GitHubにメンバーが招待されました。",
        "facts": [
          {
            "name": "担当者",
            "value": ""
          },
          {
            "name": "追加メンバー",
            "value": ""
          }
        ],
        "markdown": true
      }
    ],
    "potentialAction": [
      {
        "@@type": "ActionCard",
        "name": "GitHub メンバー一覧",
        "actions": [
          {
            "@@type": "OpenUri",
            "name": "GitHub メンバー一覧",
            "targets": [
              {
                "os": "default",
                "uri": "https://github.com/orgs/JMASystems/people"
              }
            ]
          }
        ]
      }
    ]
  }

それを Flow の「本文」に貼り付けます。
担当者の value の値を選択した状態で(見えない箇所にありますが)右下に出てくる動的なコンテンツの「login」を選択します。
※ login が複数ありますが、選択して設定した後にマウスカーソルを当てると階層が表示されるのでそこで正しいかを確認してください。(ここは「triggerBody()?['sender']?['login']」となっていれば OK)
  面倒な場合は動的なコンテンツでなく、式のタブから直接「triggerBody()」を選択して階層を記載しても大丈夫です。
f:id:ShunsukeKawai:20180719223113p:plain
続いて追加メンバーも同じ様に value の値を選択した状態で「email」を選択します。
そうすると、招待をした人の GitHub アカウント ID、招待された人のメールアドレスが埋め込まれます。

削除時のアクションも追加する

先ほどは招待時だったので条件の「いいえの場合」にさらに条件を追加します。
f:id:ShunsukeKawai:20180719223724p:plain
条件は action が「member_removed」だったらです。
f:id:ShunsukeKawai:20180719223804p:plain
招待時と同じ様に、HTTP のアクションを追加して本文を加工します。
設定した内容は以下の通りです。削除された時は GitHub のアカウント ID が取れるのでそれを設定しています。

{
  "@@type": "MessageCard",
  "@@context": "http://schema.org/extensions",
  "themeColor": "bcff4f",
  "summary": "概要",
  "sections": [
    {
      "activityTitle": "GitHubからメンバーが削除されました。",
      "facts": [
        {
          "name": "担当者",
          "value": "@{triggerBody()?['sender']?['login']}"
        },
        {
          "name": "削除メンバー",
          "value": "@{triggerBody()?['membership']?['user']?['login']}:@{triggerBody()?['membership']?['user']?['html_url']}"
        }
      ],
      "markdown": true
    }
  ],
  "potentialAction": [
    {
      "@@type": "ActionCard",
      "name": "GitHub メンバー一覧",
      "actions": [
        {
          "@@type": "OpenUri",
          "name": "GitHub メンバー一覧",
          "targets": [
            {
              "os": "default",
              "uri": "https://github.com/orgs/JMASystems/people"
            }
          ]
        }
      ]
    }
  ]
}

ここまでで Flow の設定は終わりです。(今回は追加時にはなにもしないとしています。)
保存をするとトリガーとして作成した「HTTP 要求の受信時」の「HTTP POST の URL」の箇所に URL が生成されているのでコピーします。
f:id:ShunsukeKawai:20180719224824p:plain

Github に Webhooks を追加する

GitHub の Organization の Settings から「Webhooks」を選択します。
f:id:ShunsukeKawai:20180719224938p:plain
「Add webhook」を選択して、「Payload URL」に Flow で生成された URL を貼り付けます。
「Content type」は「application/json」を選択してください。
f:id:ShunsukeKawai:20180719225352p:plain
Which events would you like to trigger this webhook? のところで「Let me select individual events.」を選択します。
するとイベントが一覧で出てくるので今回は「Organizations」を選択します。
f:id:ShunsukeKawai:20180719225454p:plain
「Add webhook」ボタンを押して完了です。

動作確認

実際にメンバーを招待してみます。
すると Teams に通知が来ました!
f:id:ShunsukeKawai:20180719225829p:plain
削除してみます。同じ様に通知が来ます!
f:id:ShunsukeKawai:20180719225949p:plain

完成!

※ 同じユーザーを招待/削除を繰り返すと GitHub の招待 Webhook が飛ばないみたいです。うまく行かない場合は GitHub の Webhook の履歴、Flow の履歴を追ってみるとどこが動いてる/動いてないは分かると思います。

de:code 2018 イベントアプリ振り返り

5/22,23 で開催された de:code 2018 に公式イベントアプリを提供しました。
de:code (decode) 2018 | 開発者をはじめとする IT に携わる全てのエンジニアのためのイベント

自分用振り返りをまとめておきます。

公式イベントアプリ機能

マイクロソフトさんのイベントアプリ開発は今回で5回目です。今までと同様に Xamarin.Forms で作成しています。
毎回新たな試みが追加されますが、今回の de:code では細かなブラッシュアップとともに以下が追加/更新されました。

セッション聴講者属性

各セッションの参加者がどんな領域を担当しているか、主に使用しているサービス、言語を表示するようにしました。
f:id:ShunsukeKawai:20180524173611p:plain:w400

会場マップの混雑状況表示

日本システムウェア 様の CityVision*1 というサービスと連携して会場の混雑状況を表示できるようになりました。
f:id:ShunsukeKawai:20180524173739p:plain:w400
f:id:ShunsukeKawai:20180524173806p:plain:w400

スタンプラリー

デンソーコミュニケーションズ 様の Sifty*2 というサービスと連携してスタンプラリーを実装しました。
f:id:ShunsukeKawai:20180524173922p:plain:w400
f:id:ShunsukeKawai:20180524173936p:plain:w400

お知らせ

今までのアプリでは何か参加者の皆様にお知らせをしたい時、Push 通知を行っていましたが、Push するほどでもない場合もあったり、通知を許可していない方、読む前に消してしまった方がお知らせを見れなくなってしまうためお知らせ画面を追加しました。まぁ今までない方が変だったと思います。

その他機能は私が担当したセッション資料の前半に記載してあります。

www.slideshare.net

よくあったお問い合わせ

(全部書いていいのかわかりませんが) よくあるお問い合わせとして以下がありました。

アプリにサインインできない

Microsoft アカウントには組織、個人のアカウントがあり、かつそれが同じメールアドレスの場合、Web の申し込みサイトでは(私たちの開発ではないので正確にはわからないですが)試してみたところ自動的に個人アカウントでのサインインになってしまう挙動を見せていました。
そのため、参加者の方は組織でサインインしているつもりでも個人で入ってしまっていて、アプリからも組織を選択するとサインインできない。というケースが多かったです。
現在、Microsoft アカウントを組織と個人が同一のメールアドレスで作成することはできなくなっていますが*3前はできていたので(私もアカウント両方持ってます)、そういう方がいらっしゃったようです。(同一メールアドレスで作成する抜け道もあるみたいですが、ここでは言及は避けます。)

スタンプラリーができない

スタンプラリーを撮影するボタンを押すとアプリが落ちる
イベントアプリでは Visual Studio App Center を利用して Crash のログを見れるようにしているのですが、そのログを見ると以下のようなログがあがってました。
f:id:ShunsukeKawai:20180524174759p:plain
f:id:ShunsukeKawai:20180524174826p:plain
発生している端末は HUAWEI の PXX 系ばかりです。私の端末は HUAWEI Mate 9 なのですが、正常に動いています。

カメラ使用に Xam.Plugin.Media*4 を利用していたのですが、そのプラグインに以下の Issue があがっていて、HUAWEI の端末に問題があったみたいです。(絶対これが原因かは断言できない気がしてきたけど…)
IllegalArgumentException on Huawei devices · Issue #496 · jamesmontemagno/MediaPlugin · GitHub

アプリで利用している Stable 版のプラグイン(Ver.3.1.3)には本 Issue の対応はまだ入っておらず、そのままアプリのバグとなってしまいました。該当された方、申し訳ありませんでした。

その他

アンケートが表示されない系は受付時の参加者パスの ID との紐づけがうまくできていないなどらしいです。ぐるぐるがなかなか終わらない系はサーバーサイドが重くなっていた、ネットワークが遅い等でアプリ側では如何ともしがたい部分もありました。

開発の反省

単純なバグや限られたパワーでの開発のため諦めた対応等、うまくできなかった部分も結構ありました。(あるのかわからんが)次回以降の反省にしたい点です。

.NET Standard 2.0 対応したい

イベントアプリは UWP でも使えるので、.NET Standard 2.0 にすると Fall Creators Update より前を切り捨てることになります。それは無理だろうな、ということで今回はあきらめました。

結構動きがもっさり(特に Android

セッション数が多かったり、聴講者属性表示機能の追加等で動きが遅くなったと思います。もっと最適化できる部分があったハズ。

iPhone X レイアウト

iPhone X に画面レイアウト(Home Bar 等)を対応するため手抜きな方法をとりました。SafeArea プロパティを True にしただけ*5です。もっとちゃんと対応したかったです。

アプリアップデート強制する?

Ver.1.0.0 がゴールデンウイーク明けにリリースされ、その後、イベントの数日前にバージョンアップしたのですが、イベント当日も Ver.1.0.0 のままの方がいらっしゃっいました。アプリ内のお知らせでも周知していたのですが、結局そのままになっていたかもしれません。
旧バージョンでは特定の操作をするとアプリが落ちるバグがあり、その Crash ログも飛んできていたため、アップデートしてほしかった…(1.0.0 はスタンプラリーとか会場マップも Coming Soon だったけど…)
アプリ起動時にバージョンをチェックしてアップデートを強制にするか迷ったのですが、リリース直後に入れてイベント当日の受付直前にアプリを起動してアップデートをしなければならないとかが発生して受付が滞ってしまったり、しょうがなくモバイルネットワークでのアップデートをするしかなくなって"ギガ"が減るとかあるかもな~と思い、やりませんでした。(工数的にやれなかった側面もありますが…)
まぁ最初からバグ仕込むな、スケジュールに余裕を持ってリリースしろってことですかね。

セッション一覧自動スクロール

セッション一覧には現在時刻以降のセッションまで自動でスクロールする機能が前回まであったのに別件の対応でデグレして機能がなくなってたみたいです…。セッション数も過去最高で自分でアプリ使っていて結構不便でした。というか マイスケジュールと同じ様に開催日でタブにした方がよくないか?

最後に

ともかくなんとか終わってよかったです。
アプリ開発/リリースと自分のセッション準備、イベント問い合わせサポート等々、しばらく忙殺されていて家族にもだいぶ負担をかけたのでしばらくはおとなしくしています。
自セッションの振り返りとデモでできなかった部分などは別記事でまとめようと思います。

Visual Studio 2017 Version 15.5 にアップデートしたら Android が表示されなくなった時の対処方法

qiita.com
↑のクリスマスイブ分の穴埋めです。

やっつけですが、ご容赦を。


Visual Studio が 2017 になってからアップデートが短いサイクルで行われるようになりました。
一応、ReleaseNote はぺろーっと眺めてから(なにも考えず)実行しているのですが、問題が発生しました。
Visual Studio 2017 15.5 リリース ノート

久しぶりに Xamarin.Forms のプロジェクトを開いてみると、ツールメニューから「Android」がなくなりました。
なにを言ってるかわからないかもしれませんが、なくなりました。
f:id:ShunsukeKawai:20171226154456p:plain:w400

↓↓↓正しい姿↓↓↓
f:id:ShunsukeKawai:20171226155422p:plain:w400

Androidデバッグ実行もアーカイブもできなくなっています。

対処方法

Visual Studio Installer を起動して以下の2つを選択してからインストールを実行します。
f:id:ShunsukeKawai:20171226154805p:plain

Ver.15.5 から Android 標準の SDK Manager とは別に Xamarin 用のが追加されたようですね。
そしてそれはデフォルトではインストール対象外になっているようです。。
今回の問題自体は Android SDK セットアップ(API レベル 25)が原因らしいですが、SDK Manager も一緒に入れちゃいましょう。

【参考:Xamarin Android SDK Manager の画面】
f:id:ShunsukeKawai:20171226160607p:plain

蛇足

ちゃんと開発環境の更新をする時は変更内容を確認しましょう。(自戒)
でもアップデートが頻繁過ぎてつらいなぁ。
Xamarin も VS に含まれて更新されるから全然関係ない更新もあるし、個別に更新してた時の方が楽だったかもな。
チームメンバー内で環境を統一するのは楽になったけど、1週間に1回ぐらい更新すべきかを判断して更新するって(30分もかからないけど)タスクが増えちゃう。
みんなめんどくさくないのかなぁ?

Xamarin.Forms 製アプリを iPhone X で動かしてみた(やっべぇぞ

Visual Studio for Mac | Xamarin Releases
ここに書いてある方法で Visual Studio for MaciOS 11 を動かせるようにしました。

んで、Xamarin.Forms で簡単なアプリを実行してみました。
こんな XAML です。よくある iOS 用に画面上部の Padding に 20 設定してあるヤツです。
嫌な予感はしていました。

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="test.Views.MyPage" BackgroundColor="Teal">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="Android, WinPhone">0</On>
            <On Platform="iOS">0,20,0,0</On>
        </OnPlatform>
    </ContentPage.Padding>
    <ContentPage.Content>
        <StackLayout>
            <Label Text="Xamarin.Forms iPhone X Test" />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

で実行すると…

f:id:ShunsukeKawai:20170915154301p:plain
ぐわー、やっぱりー。
いろんなアプリ見直さなきゃーー

#どうでもいいけど、シュミレーターのスクショがカメラの部分透過になるんだな

==================
20170915_追記1
NavigationPage を親にして先ほどの画面を表示するとこんな感じになります。
f:id:ShunsukeKawai:20170915202653p:plain

==================
20170915_追記2
以前作成した de:code 2017 の公式アプリを動かしてみました。意外になんとかなってる…のかな…
f:id:ShunsukeKawai:20170915211439p:plain:w200 f:id:ShunsukeKawai:20170915211447p:plain:w200 f:id:ShunsukeKawai:20170915211456p:plain:w200

Xamarin.Forms で iOS のステータスバーとナビゲーションバーの色を動的に変えたい(仮)

前回の記事ではナビゲーションバーに色付きアイコンを置けるようにしました。
shunsukekawai.hatenablog.com


ナビゲーションバー系繋がりで、ステータスバーとナビゲーションバー自体の色をページによって動的に変えたかったのでやってみました。
タイトルに(仮)が付いてるのは結構無理やりというか、もうちょいスマートなやり方ないのかなー。と思ってるからです。誰か教えてください。

完成形

f:id:ShunsukeKawai:20170907203753g:plain:w300

作成方法

PCL 側にこんなカスタムコントロールを作成します。

ナビゲーションバーの色(NavigationBarColor)とステータスバーのスタイルをデフォルトか(IsUseDefaultBarStyle)の BindableProperty を追加した ContentPage を継承したクラスです。

iOS の プロジェクトに CustomRenderer を作成します。
  • OnElementChanged で上で作成したプロパティを取得してメンバー変数に格納
  • ViewWillAppear でステータスバーの種類を設定、ナビゲーションバーがある場合は色を設定
  • 右側のアイコンボタンの種類を設定(前回の記事分のアイコンにモードを設定するヤツ)


iOS プロジェクトの info.plist に View controller-based status bar appearance というプロパティを追加し、NO を設定します。

これをしないとステータスバーのスタイルが変更できません。
f:id:ShunsukeKawai:20170907214852p:plain

各ページをカスタムコントロールにして、プロパティを設定

以下のように遷移するようになってます。今までのもひっくるめて Github にアップしておきます。
MainPage→SecondPage
      ↓
     ModalPage
github.com

Xamarin.Forms で iOS のナビゲーションバーに色付きアイコンを置きたい

Xamarin.Forms の NavigationBar のアイコンを設定したい場合、以下のように ToolbarItems を使ってアイコンを追加します。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="ColorNavigationBarIcon.Views.MainPage"
             Title="MainPage">
    <ContentPage.ToolbarItems>
        <ToolbarItem Icon="Icon-Small.png"></ToolbarItem>
    </ContentPage.ToolbarItems>
  <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
    <Label Text="{Binding Title}" />
  </StackLayout>
</ContentPage>

Icon-Small.png は Xamarin.Forms のプロジェクトテンプレートからプロジェクトを作成した時に勝手に追加されるこのアイコンです。
f:id:ShunsukeKawai:20170901212120p:plain

で、このアプリを実行してみると…
f:id:ShunsukeKawai:20170901220106p:plain:w300
となります。塗り潰されとる。
Apple の高尚な思想なのかなんなのか知らないですが、デフォルト単色になっちゃうみたいです。

試しに App.xaml とかのリソースでスタイルを定義して NavigationPage のテキストに色を付けてみます。

<Application.Resources>
    <ResourceDictionary>
        <Style TargetType="NavigationPage">
            <Setter Property="BarTextColor" Value="Red" />
        </Style>
    </ResourceDictionary>
</Application.Resources>

で、実行すると…
f:id:ShunsukeKawai:20170901220144p:plain:w300
赤くなります。どうやら BarTextColor に定義されてる色で塗り潰されるのかな。

あの六角形のアイコンが見たいです。

まずは Xamarin に関係なくネイティブでのやり方を調べてみます。
参考:[Swift]UIImageのレンダリングモードまとめ - Qiita
ほうほう、UIImageRenderingMode とかいうヤツがなんかしてるんだな。

ということで、iOS のプロジェクトに Rendererを追加します。
色々やった結果、以下でなんとかなりました。

using ColorNavigationBarIcon.iOS.Renderers;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ExportRenderer(typeof(ContentPage), typeof(ColorIconContentPageRenderer))]
namespace ColorNavigationBarIcon.iOS.Renderers
{
    public class ColorIconContentPageRenderer : PageRenderer
    {
        public override void ViewWillAppear(bool animated)
        {
            base.ViewWillAppear(animated);

            if (this.NavigationController == null) return;

            var items = this.NavigationController.TopViewController.NavigationItem;

            foreach (var item in items.RightBarButtonItems)
            {
                if (item.Image == null) continue;

                item.Image = item.Image.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);
            }
        }
    }
}

じゃーん
f:id:ShunsukeKawai:20170901220157p:plain:w300
わーい

de:code 2017(Day 1 / Day 2)が終わって

●本記事はポエムです。

2017/5/23-24 に開催された de:code 2017 の公式アプリを Xamarin.Forms で開発しました。

イベントアプリ自体は 2016/9 の Microsoft Foresight、2016/11 の Microsoft Tech Summit に続き3回目なのですが、de:code はデベロッパー向けのイベントのため今までとは反応が違うんだろうなぁ、と開催前から思っていました。
イベント自体の来場者数は Tech Summit と大差ないのにアプリのインストール数もざっくり3倍ぐらいです。
予想通りアプリ公開直後から様々なご意見が散見されて中には手厳しいものもあり、 Twitter を見るのが怖くなったり、イベントが継続不可能になるほど炎上している夢を見たり、自分のセッションの準備もあるわでだいぶてんやわんやでした。。

結果として正直、反省点だらけです。

スタンプラリー

今回新たに追加したスタンプラリー機能ですが、メッセージが適切でなかったり(iOS では位置情報の許可も必要なのに Bluetooth が無効と延々表示される)、スタンプが取得できないと問い合わせをいただいたりもしました。
これは確認漏れや、端末の個体差、会場の電波状況等々、こちらの考慮不足です。おそらく全員が問題なく使用できるようにするというより、Beacon を使用したスタンプの取得ができない方の救済措置があった方がよかったと思いました。QR コードや最悪、紙での代替などを用意しておけばよかったです。
元々、ブース出展社の方のお手を煩わせないように、という目論見もあり Beacon のみで行うことになったのですが、逆に問い合わせ対応が増えてしまったようです。
うまくできなかった方、問い合わせ対応で時間が取られた出展社の方にはご迷惑をおかけしました。申し訳ありません。

その他問題

アプリのリリース時期も色々あって、イベントの数日前になってしまいフィードバックを反映する時間もなく、ぶっつけ本番的な感じになってしまいました。
また、サーバー側の DB/API はウチの担当ではなかったですが、パスワードに記号が入ってるとログインできない(ログイン API 関連)、QR コードが出てこない(参加情報取得 API で正しい値が取得できない)、アンケートが出てこない(イベント受付時のデータ登録がうまくできてない)等々、アプリ外の問題も発生していて想定が甘かった箇所もありました。

Xamarin.Forms での開発

開発に関しては、まぁこれでいーんじゃない?とりあえず作りあげよう精神で突き進んで来ました。
今回は細かい部分の調整をする余裕があまりなかったため、利用者の方々にはご不満やご不便をおかけすることもあったかと思います。
「逆に Microsoft の技術のネガキャンになっちゃうんじゃない?」的なご意見も見かけました。
個人的には、Xamarin.Forms で全プラットフォームに対してベストなアプリを作るのは潤沢な工数、期間がないと難しく、Xamarin.Forms はそれなりの工数でそれなりのアプリを作れる(素晴らしい)開発環境だと思っています。(というか潤沢にあってベストまたはそれに近い UI を実現するのであれば Forms 使わない方がいいと思ってる。もちろんプラットフォームの理解も必要だけど。)
==================

  • ちょっと誤解を招きそうなので追記

当然ながら今回のアプリが Xamarin.Forms の限界なわけではありません。
色々妥協はしていますし、私たちがいたらない箇所も多々あったと思います。
==================

メモ:次回まで改善したい(とりあえず思いつくまま)

全体

  • ログインしなくても使える機能は開放する
  • セッション編集画面の自動スクロールの時間が Hour でやっているため次の次の時間帯にスクロールしてしまう可能性がある
  • フィルターの全クリア処理追加
  • 問い合わせ先を明記する
  • パスワード忘れのリンクをログイン画面に置く
  • ログイン時エラーの場合は中途半端なデータを作らない
  • マップ画像キャッシュ系(更新された時のためにキャッシュを無効にしているが、オフラインだと取得できない)
  • UI 全体的に見直し
  • スタンプラリーができない場合の救済措置(QR コードなど)
  • 空席状況をもっと厳密に出したかった
  • アプリ更新があった際のストアへの誘導

iOS

  • ステータスバーの色
  • Bluetooth と位置情報が必要なのに Bluetooth のメッセージしかでない
  • スクロールのパフォーマンス

Android

  • ドロップダウンのデザイン
  • ログイン画面のテキストの配色
  • 画像ピンチインやりにくい
  • 戻るでアプリ終了しない、もしくは再開時に復帰する
  • Push 通知が折り返さない、展開できない

UWP

  • 画像ピンチインできない
  • 早くスクロールすると黒くなる

さいごに

反省点も多々ありますが、なんとか終わり、ポジティブな感想をいただくとやっぱりうれしかったです。
そういったご意見にだいぶ救われました。
また、ウチはアプリ開発を担当しただけでイベント運営ではないですが、運営側に勝手に口出しして色々中の様子を見ることができ、とても勉強になりました。
運営の方々はほんとに大変そうで、来場者の方をどう満足させるかに全力を注いでいて、どんなイベント、サービスもたくさんの人々の努力によって形成されてることを改めて認識しました。
自分がユーザーの立場になった際にはいいと思った点もちゃんとフィードバックしようと深く心に刻みました。
(去年の de:code は参加者側で文句言いまくってた)

Visual Studio 2017 Version 15.2 にアップデートしたら Xamarin.Forms の Android アプリがすごい遅くなったから回避した

============================================
2017/5/31 追記
Visual Studio 2017 version 15.2 (26430.12) で対応されたようです。
>Fixed a performance dedgration sometimes occuring in Xamairn Android apps.
Visual Studio 2017 Release Notes
============================================

============================================
2017/5/25 追記
Mac、VS 2015 は対応されたようです。
Stable Release: 15.2.2 Servicing Release | Xamarin Releases


2017 はまだかな…
============================================

============================================
2017/5/17 追記
下記手順を行うと Android の Release ビルドで作成したアプリがうまく動かなくなります。
Debug 時の回避手段とお考え下さい。
============================================

※この記事の手順は自己責任でお願いします。

タイトルの通りです。
Xamairn.Forms、Prism(Unity) で作成した Android アプリがなんか InitializeComponent で数秒かかる感じに遅くなったんです。
Android 4.4では発生せず、6.0、7.0で現象を確認できました。

環境は以下の通り。

  • Windows 10 Pro Creators Update (バージョン1703、ビルド15063)
  • Visual Studio 2017 Version 15.2 (26430.4)
  • Xamarin 4.5.0.443
  • Xamarin.Android.SDK 7.3.0.13


色々皆さんのご協力をいただき、対症療法的に回避しました。

調べていると下記 bugzilla がヒットしてみんな Xamarin.Android 7.3 が原因っぽいことを言っています。

56240 – Performance Degradation When Using Expressions

ということで Xamarin.Android を前のバージョンにダウングレードしたいと思います。

下記サイトから以前の Xamarin インストーラー(4.4)をダウンロードしてきます。

※ Xamarin のアカウントが必要です。
https://store.xamarin.com/account/my/subscription/downloads
f:id:ShunsukeKawai:20170515215324p:plain

ダウンロードした Xamarin インストーラーを起動します。

※この時点で Build Tools 2015 が必要です。みたいなことを言われたらインストールしてください。
Download Microsoft Build Tools 2015 from Official Microsoft Download Center

インストーラー起動後、今回は Xamarin.Androidだけ必要なので、他のチェックは外しちゃいます。

f:id:ShunsukeKawai:20170515215944p:plain

インストールされたファイルは基本的には以下にあると思います。

C:\Program Files (x86)\MSBuild\Xamarin

実際に VisualStudio で使用している Xamarin.Android のフォルダーをリネーム(バックアップ)して先ほどのファイルを丸ごと持ってきます。

C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\Xamarin
f:id:ShunsukeKawai:20170515220606p:plain

以上で終了です。

Visual Studio を起動して出力の Xamarin Diagnostics に以下のように 7.2 だよー的なのが出ていれば成功です。
早くなりました。
f:id:ShunsukeKawai:20170515220830p:plain

Prism との組み合わせなのかなー。すぐ修正されるといいなぁ。

以下、みなさんのご協力



Daiki Kawanuma (@Santea3173) | Twitter
さんが
クワーティ (@qwerty2501) | Twitter
さんも悩んでいたことを教えてくれて



かむ (@muak_x) | Twitter
さんも参戦し


Mac 版はサクッと解決してくれたので

参考にして Windows も回避です。

Backlog の更新を Zapier 経由で Microsoft Teams に通知する

一部のプロジェクトの課題管理には Nulab 社の Backlog を利用しています。
オンライン プロジェクト管理 & コラボレーションツール Backlog

標準では通知はメールベースなのでいろんな通知がわんさか来てなにがなんだかわからなくなります。
メールからの脱却みたいな話もありますし。

でチャットコミュニケーションツールには Slack と Microsoft Teams を利用しています。
Slack に関しては様々な記事があがっているので今回は 最近ローンチされた Microsoft Teams に対して通知を飛ばしたいと思います。
Microsoft Teams – Group Chat software

手順

  • Zapier でトリガーを追加する
  • Backlog から Webhook を追加する
  • Microsoft Teams に コネクタを追加する
  • Zapier でアクションを追加する

詳細

Zapier でトリガーを追加する

Zapier とはサービスとサービスを連携してくれるハブサービスです。似たような有名どころで IFTTT とかでしょうか。
Zapier | The easiest way to automate your work

  • サインアップ

適当にサインアップします。
無料枠だと、2ステップまでとか制限がありますが、今回は無料で大丈夫です。
サインアップ後、画面上の「MAKE A ZAP!」ボタンを押します。
f:id:ShunsukeKawai:20170406162913p:plain:w600

  • Trigger 登録

Zapier をキックするトリガーを登録します。
今回ですと、Backlog の更新です。
様々なサービスから Webhooks by Zapier を選択します。
Webhook とは POST リクエストをサクっと呼んでくれる仕組みです。
f:id:ShunsukeKawai:20170406163234p:plain:w600
次の画面で Catch Hook を選択して「Save + Continue」ボタンを押します
f:id:ShunsukeKawai:20170406163626p:plain:w600
次の画面の Pick off a Child Key はブランクのまま「Continue」ボタンを押します。
そうすると Webhook の URL が自動生成されるので、URL をコピーしておきます。
f:id:ShunsukeKawai:20170406163912p:plain:w600
「Test this Step」は後程やるので、ブラウザは開いたままで次の Backlog の設定に進みます。

Backlog から Webhook を追加する

先ほど作成したトリガーを呼び出す仕組みを Backlog 側に追加します。
Backlog のプロジェクト設定画面のメニューから「Webhook」を選択して「Webhookを追加する」ボタンを押します。
f:id:ShunsukeKawai:20170406162057p:plain
適当な名前を付けて、先ほど Zapier 側で生成されて URL を指定します。
イベントは今回は課題の追加に設定しておきます。
f:id:ShunsukeKawai:20170406165150p:plain

送信をテストする

後述する Teams 側に通知する内容を設定するために一回テストします。
Backlog ⇒ Zapier の送信テストをするとアクションで設定する内容が展開されます。

Backlog の Webhook 設定画面の下部にある実行テストを表示しておきます。(まだ押しても意味ないです。)
f:id:ShunsukeKawai:20171219171311p:plain
Zapier の画面に戻って「OK,I did this」ボタンを押します。
f:id:ShunsukeKawai:20171219171708p:plain
そうすると受信を待機している状態になるので、Backlog の画面でテストを実行します。
f:id:ShunsukeKawai:20171219171737p:plain
「Test Successful!」と表示されれば成功です。
f:id:ShunsukeKawai:20171219172304p:plain
Backlog 側の設定は以上です。
Zapier の画面はそのままにして次のステップに進みます。

Microsoft Teams に コネクタを追加する

実際に Teams に投稿するためのコネクタを追加します。
Teams のチャンネルのコンテキストメニューから「コネクタ」を選択します。
f:id:ShunsukeKawai:20170406165624p:plain
コネクタの一覧から Incoming Webhook を追加します。
f:id:ShunsukeKawai:20170406165804p:plain:w600
適当な名前と画像を設定して「作成」ボタンを押します。
f:id:ShunsukeKawai:20170406165933p:plain:w600
そうすると自動的にURLが生成されるので、コピーして「完了」ボタンを押します。
f:id:ShunsukeKawai:20170406170136p:plain:w600
Teams 側の設定は以上です。
※ ちゃんと「完了」ボタンを押しておきましょう。押していない状態でTestしたら飛ばないなぁってしばらく悩んでました…

Zapier でアクションを追加する

Zapier の画面に戻ってアクションを追加します。
こちらもトリガーと同じ様に Webhooks by Zapier を選択します。
f:id:ShunsukeKawai:20170406171244p:plain:w600
次の画面で POST を選択して「Save + Continue」ボタンを押します。
f:id:ShunsukeKawai:20170406171351p:plain
次の画面では実際に Teams に通知する内容を設定します。
URL には先ほど Teams 側で生成された URL を設定します。
Payload Type は Json を設定します。
f:id:ShunsukeKawai:20170406173620p:plain
Data には Teams でどのような内容を通知するかを指定します。
こちらは Teams 側のお作法に準拠してください。以下のサイトが参考になります。
Actionable message card reference | Microsoft Docs

Zapier は STEP1 で設定したトリガー側から送信されるデータ(今回は Backlog の Webhook からのデータ内容)を以下のように選択できるようになっています。めっちゃ便利です。
f:id:ShunsukeKawai:20170406172209p:plain

例として以下の設定にしてみました。
f:id:ShunsukeKawai:20170406175426p:plain
Backlog は Project Key と Content ID の組み合わせで URLが生成されるので、通知から直接リンクできるようにしています。

それ以外の設定はデフォルトのままで大丈夫です。
「Save + Continue」ボタンを押すと自動的にテストが走って、
f:id:ShunsukeKawai:20170406180202p:plain
と Teams に通知されます!
やった!

おまけ

今回はアクションに Webhook を使用していますが、アクションのアプリに Teams が追加される動きもあるみたいです。
Microsoft Teams Integrations | Zapier

以上です。

Visual Studio 2017でXamainのUpdate Channelを切り替える方法(というかVisual Studio 2017のインストーラーの仕組み)

Visual Studio 2015 では Xamarin の最新の機能を試したい場合、ツール⇒オプション⇒Xamarin⇒その他でチャンネルを切り替えることができました。
しかし、昨日リリースされた Visual Studio 2017 ではその設定がなくなっています。

Visual Studio 2017

f:id:ShunsukeKawai:20170309112803p:plain

で、どうするかと言うと、Xamarin 公式サイトに答えがありました。
Change the Updates Channel - Xamarin
ちゃんとすぐ更新されていて素晴らしいですね。
結論から言うと、Xamarin 個別にチャンネルを切り替えることができなくなり、Visual Studio 自体のバージョンにより Xamarin のリリースも制御されるようになったみたいです。

インストール手順

プレビュー版の Visual Studio インストーラーをダウンロード

Visual Studio Preview Release Notes
のサイトの以下のリンクからプレビュー版のインストーラーをダウンロードします。
f:id:ShunsukeKawai:20170309113900p:plain
※ このサイトには Visual Studio Enterprise 用のインストーラーリンクしかありませんが、別のエディションをインストールしたくてもそのままダウンロードしてください。

インストールしたいエディションを選択して実行

ダウンロードしたインストーラーを起動すると以下のような画面が開きます。
f:id:ShunsukeKawai:20170309115551p:plain
このまま進むと Enterprise 版になってしまうので、一旦右上の×で閉じます。
そうすると以下の画面になるので、好きなエディションを選択して、インストールに進みます。
f:id:ShunsukeKawai:20170309120422p:plain
その際にインストールのニックネームを付けることでどのバージョンかとかがわかるようできるみたいです。
f:id:ShunsukeKawai:20170309122949p:plain

ここで疑問

今回やりたいことは Xamarin の Update Channel を切り替えたいだけなのに VS 自体を別でインストールしないといけないとしたらディスク容量はすぐ一杯になってしまうでしょうよ。
インストールする対象を Xamarin だけにしてちゃんと開発できるのか?(上の画像は Xamarin だけを選んだ。容量3.98GB)
ちょっと私のディスク容量はすでにカツカツなので、どなたか確認したら教えてくださいm(_ _)m

Visual Studio 2017 のインストーラーの仕組み

今回からインストーラー自体がアプリとして自動的に更新されるような仕組みになったみたいです。
それぞれのエディションごとにダウンロードリンクは別で起動時の対象は違うけど上述の通り、全部選べる。
また、すでに製品版を既にインストールしている端末でプレビュー版のインストーラーを起動すると、インストーラーが更新されて以下のように製品版もプレビュー版も管理できるようになります。
f:id:ShunsukeKawai:20170309123908p:plain

そもそも、OS の「プログラムと機能」からは Visual Studio 2017 の変更が選べず、アンインストールしかありません。
ここにある Visual Studio 2017 は VS のことではなく、インストーラーを指すようです。(表示されているバージョンもインストーラーのバージョン)
f:id:ShunsukeKawai:20170309124225p:plain
インストーラーを使い捨てのモノでなくアプリとして作成することで、製品の更新や管理を統合的にできる仕組みになった感じですかね。

本来の目的は達成できてないけど、今日はここまで。

Xamarin.FormsでiOSのEditorの枠線をEntryみたいにしたい

Xamarin.Forms で複数行のテキストボックス(Editor)を配置した場合、以下の様になります。
f:id:ShunsukeKawai:20170209173238p:plain:w300

ちゃんと置いてあるのに真っ白です。

<StackLayout Margin="20">
    <Editor HeightRequest="150"/>
</StackLayout>

入力はもちろんできます。
f:id:ShunsukeKawai:20170209173530p:plain:w300

うーん(´・ω・`)これが iOS では標準なのか?

Xamarin.Forms の標準コントロールがどのネイティブコントロールマッピングされているかは以下のページに書いてあります。
Renderer Base Classes and Native Controls - Xamarin
Entry は UITextField に Editor は UITextView にマッピングされてます。


ということで Effect です。
といっても標準の UITextField の Border がどうなってるのかよくわからなかったので、アナログ戦法で寄せました。

  • iOS のプロジェクトに以下を追加します。

BorderEffect.cs

using EditorBorder.iOS.Effects;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ResolutionGroupName("Effects")]
[assembly: ExportEffect(typeof(BorderEffect), "BorderEffect")]
namespace EditorBorder.iOS.Effects
{
    public class BorderEffect : PlatformEffect
    {
        protected override void OnAttached()
        {
            Control.Layer.BorderWidth = 0.6f;
            Control.Layer.BorderColor = Color.FromRgb(209, 209, 209).ToCGColor();
            Control.Layer.CornerRadius = 5;
        }

        protected override void OnDetached()
        {
            Control.Layer.BorderWidth = 0.0f;
        }
    }
}
  • PCL のプロジェクトに以下を追加します。

BorderEffect.cs

using Xamarin.Forms;

namespace EditorBorder.Effects
{
    public class BorderEffect : RoutingEffect
    {
        public BorderEffect() : base("Effects.BorderEffect")
        {
        }
    }
}
  • 画面の XAML に作成した Effect を適用します。

Effect は違うコントロールにも適用できるので、とりあえず見た目を確認するため生の Entry と適用した Entry を並べます。
MainPage.xaml

<StackLayout Margin="20">
    <Entry Text="標準のEntry" />
    <Entry Text="EffectしたEntry">
        <Entry.Effects>
            <effects:BorderEffect />
        </Entry.Effects>
    </Entry>
</StackLayout>

で実行すると…
f:id:ShunsukeKawai:20170209174941p:plain:w300

お!いい感じやん!

って事で、本来の目的である Editor に Effect を適用します。
MainPage.xaml

<StackLayout Margin="20">
    <Entry Text="標準のEntry" />
    <Entry Text="EffectしたEntry">
        <Entry.Effects>
            <effects:BorderEffect />
        </Entry.Effects>
    </Entry>
    <Editor HeightRequest="150">
        <Editor.Effects>
            <effects:BorderEffect />
        </Editor.Effects>
    </Editor>
</StackLayout>

結果

f:id:ShunsukeKawai:20170209175625p:plain:w300

いいんじゃないでしょうか。
違うコントロールに適用できる Effect の便利さを改めて認識しました。

【続】Xamarin.Formsでぺこぺこ凹むアイコンボタンを作った

先日アイコンボタンを作ったと書きましたが、色々更新しました。
shunsukekawai.hatenablog.com

f:id:ShunsukeKawai:20170110224311g:plain

追加内容

  • ラベル追加(DescriptionText)
  • ラベル位置プロパティ設定(DescriptionPosition)
  • ラベルサイズプロパティ設定(DescriptionSize)
  • ラベルカラープロパティ設定(DescriptionColor)
  • アイコン高さプロパティ設定(IconHeight)
使い方

なんか設定する項目が多くなってヤダけど、しょうがない。
ラベルが必要なければ設定しなくて OK です。

<controls:IconButton
    DescriptionColor="#636161"
    DescriptionPosition="Bottom"
    DescriptionSize="24"
    DescriptionText="削除"
    HeightRequest="120"
    HorizontalOptions="Center"
    IconHeight="70"
    IconMargin="10"
    IconSource="{Binding Source=IconButtonSample.Images.Trash.png, Converter={StaticResource ImageSourceConverter}}"
    VerticalOptions="Center">
    <controls:IconButton.GestureRecognizers>
        <TapGestureRecognizer Command="{Binding Path=DeleteCommand}" />
    </controls:IconButton.GestureRecognizers>
</controls:IconButton>

めんどくさかったところ

  • 位置を設定する

横着してボタン全体を囲う2行2列の Grid を追加して位置プロパティ(Enum)によってボタンとラベルの位置を設定しましたが、
Xamarin.Forms なのか UIView なのかわかりませんが、Grid の Definition が Auto かつコントロールが何もなくても微妙にスペースが空いちゃうので、RowSpacing と ColumnSpacing を 0 に設定しています。

<?xml version="1.0" encoding="utf-8" ?>
<ContentView
    x:Class="IconButtonSample.Controls.IconButton"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
    <Grid
        x:Name="grdIconButton"
        ColumnSpacing="0"
        HorizontalOptions="CenterAndExpand"
        RowSpacing="0"
        VerticalOptions="CenterAndExpand">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <Image
            x:Name="imgIcon"
            Aspect="AspectFit"
            BindingContextChanged="imgIcon_BindingContextChanged" />
        <Label
            x:Name="lblDescription"
            HorizontalOptions="CenterAndExpand"
            VerticalOptions="CenterAndExpand" />
    </Grid>
</ContentView>
private void setPosition(DescriptionPosition position)
{
    switch (position)
    {
        case DescriptionPosition.None:
            imgIcon.SetValue(Grid.RowProperty, 0);
            lblDescription.SetValue(Grid.RowProperty, 0);
            imgIcon.SetValue(Grid.ColumnProperty, 0);
            lblDescription.SetValue(Grid.ColumnProperty, 0);
            lblDescription.IsVisible = false;
            break;
        case DescriptionPosition.Left:
            imgIcon.SetValue(Grid.RowProperty, 0);
            lblDescription.SetValue(Grid.RowProperty, 0);
            imgIcon.SetValue(Grid.ColumnProperty, 1);
            lblDescription.SetValue(Grid.ColumnProperty, 0);
            break;
        case DescriptionPosition.Top:
            imgIcon.SetValue(Grid.RowProperty, 1);
            lblDescription.SetValue(Grid.RowProperty, 0);
            imgIcon.SetValue(Grid.ColumnProperty, 0);
            lblDescription.SetValue(Grid.ColumnProperty, 0);
            break;
        case DescriptionPosition.Right:
            imgIcon.SetValue(Grid.RowProperty, 0);
            lblDescription.SetValue(Grid.RowProperty, 0);
            imgIcon.SetValue(Grid.ColumnProperty, 0);
            lblDescription.SetValue(Grid.ColumnProperty, 1);
            break;
        case DescriptionPosition.Bottom:
            imgIcon.SetValue(Grid.RowProperty, 0);
            lblDescription.SetValue(Grid.RowProperty, 1);
            imgIcon.SetValue(Grid.ColumnProperty, 0);
            lblDescription.SetValue(Grid.ColumnProperty, 0);
            break;
        default:
            break;
    }
}

こちらも更新しています。
github.com

Xamarin.Formsでぺこぺこ凹むアイコンボタンを作った

Image だけをボタンにしたいケースが多々あり、TapGestureRecognizer で簡単に作れるのですが、押した感がなくイマイチだし好みに合うヤツが見当たらなかったので作りました。
f:id:ShunsukeKawai:20170106115747g:plain
わかりにくいですが、黒い背景でも微妙にボタン裏の色が変わっています。

凹む処理はここを参考にしました。
app-evolve/FavoriteImage.cs at master · xamarinhq/app-evolve · GitHub

最初は Image コントロールだけを拡張していましたが、それだと凹ますと背景色まで凹んでしまいかっこ悪かったので Content View の中に Image を入れました。

IconButton.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentView
    x:Class="IconButtonSample.Controls.IconButton"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
    <Image x:Name="imgIcon" BindingContextChanged="imgIcon_BindingContextChanged" />
</ContentView>
IconButton.xaml.cs
using System;
using System.Threading.Tasks;

using Xamarin.Forms;

namespace IconButtonSample.Controls
{
    public partial class IconButton : ContentView
    {
        private bool addedAnimation;

        public static readonly BindableProperty IconSourceProperty =
            BindableProperty.Create(
                "IconSource",
                typeof(ImageSource),
                typeof(IconButton),
                null,
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    ((IconButton)bindable).IconSource = (ImageSource)newValue;
                }
            );

        public ImageSource IconSource
        {
            get { return (ImageSource)GetValue(IconSourceProperty); }
            set
            {
                SetValue(IconSourceProperty, value);
                imgIcon.Source = value;
            }
        }

        public static readonly BindableProperty IconMarginProperty =
            BindableProperty.Create(
                "IconMargin",
                typeof(Thickness),
                typeof(IconButton),
                new Thickness(0),
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    ((IconButton)bindable).IconMargin = (Thickness)newValue;
                }
            );

        public Thickness IconMargin
        {
            get { return (Thickness)GetValue(IconMarginProperty); }
            set
            {
                SetValue(IconMarginProperty, value);
                imgIcon.Margin = value;
            }
        }

        public IconButton()
        {
            InitializeComponent();
        }

        private void imgIcon_BindingContextChanged(object sender, EventArgs e)
        {
            if (addedAnimation || GestureRecognizers.Count == 0)
                return;

            var tapGesture = GestureRecognizers[0] as TapGestureRecognizer;
            if (tapGesture == null)
                return;

            tapGesture.Tapped += (tapsender, tape) =>
            {
                Device.BeginInvokeOnMainThread(async () => await tappedAnimation());
            };

            addedAnimation = true;
        }

        private async Task tappedAnimation()
        {
            BackgroundColor = Color.FromRgba(68, 68, 68, 70);
            await imgIcon.ScaleTo(0.8, 75);
            await imgIcon.ScaleTo(1.0, 75);
            BackgroundColor = Color.Transparent;
        }
    }
}

凹ます処理の前後に Content View 自体の背景色を適当な灰色にしています。
ポイントとして Alpha 値を設定して透過にすることで画面の背景色になじむようにしています。
本来はこの辺も BindableProperty にして公開した方がよかったかもしれませんが、まぁいいかなと。

使う画面.xaml
<control:IconButton
    HeightRequest="50"
    HorizontalOptions="Center"
    IconMargin="10"
    IconSource="{Binding Source=IconButtonSample.Images.Trash.png, Converter={StaticResource ImageSourceConverter}}"
    VerticalOptions="Center">
    <control:IconButton.GestureRecognizers>
        <TapGestureRecognizer Command="{Binding Path=DeleteCommand}" />
    </control:IconButton.GestureRecognizers>
</control:IconButton>

ImageSource を Converter 使っていますが、これは画像を埋め込みリソースにして PCL 側に置いているからです。
その辺も含めてこちらにまるごとサンプルを置いてありますので、ご自由にどうぞー。
Prism 使ってます。
github.com

あとは指が抑えている状態(Hold 時)にも色変えたいけど、とりあえず標準のヤツよりいいから満足です。

更新しました

shunsukekawai.hatenablog.com

Xamarin.Forms と Mobile Engagement でユーザー分析しちゃえ!

Xamarin Advent Calendar 2016 - Qiita "その1" 9日目の投稿です。

はい、ということでこんな状況の中、なんとか書きました。。


ホントは0時きっかりにアップしたかったけど、ご了承ください。

さて、Microsoft さんのイベントで使用したアプリケーションの開発をやった話の記事で簡単に説明しましたが、Azure の Mobile Engagement というサービスはとても楽しいサービスです。
shunsukekawai.hatenablog.com

本記事では Xamarin.Forms でどのように Mobile Engagement でどんなことができるのか、どう使用するかを解説をします。

Mobile Engagement でできること

リアルタイムのアクティブユーザー

こんな感じで今正にアクティブなユーザー数が表示されます。
f:id:ShunsukeKawai:20161102190000p:plain

どの画面がどれだけ使われているか

画面の起動時にコードを1行埋め込むことで、勝手にデータを蓄積してくれます。
f:id:ShunsukeKawai:20161102190017p:plain

どんな画面遷移を行っているか

どこからどこの画面に行ってるかよくわからん図で表示されます。
f:id:ShunsukeKawai:20161208225124p:plain

セッション時間はどのくらいか

どのくらいアクティブにアプリを使ってくれているのかが表示されます。
f:id:ShunsukeKawai:20161208225753p:plain

バイスはなにか

iOS(iPhone9,1とかなんなのか…)
f:id:ShunsukeKawai:20161208225228p:plain
Android は製造元を選択するとモデルまで表示されます。
f:id:ShunsukeKawai:20161208230027p:plain

OSはなにか

サポート対象OSを決める時に役に立ちそう。
f:id:ShunsukeKawai:20161208225325p:plain

どのアプリバージョンを使っているか

ちゃんとユーザーがバージョンアップしてくれてるかわかりますね。
f:id:ShunsukeKawai:20161208225357p:plain

画面サイズはなにか

どのサイズで動かされているかわかると色々対策できそうです。
f:id:ShunsukeKawai:20161208225512p:plain

アプリがクラッシュしている原因とその詳細

StackTrace 的なのも出てます。
f:id:ShunsukeKawai:20161208225632p:plain

と、まぁここに挙げたのは一部ですが、見てるだけでも酒の肴になりそうですね。

Xamarin.Forms に組み込む方法

ここからは Mobile Engagement をどうやってXamarin.Forms に組み込むかです。

Azure ポータルから Mobile Engagement のサービスを追加

f:id:ShunsukeKawai:20161201212016p:plain:w500

  • 対象プラットフォームを UWP、AndroidiOSを選択する

f:id:ShunsukeKawai:20161201215231p:plain:w500

  • その他設定はよしなに
  • 作成完了(3つのアプリが作成されます)

Mobile Engagement アプリはプラットフォームごとに作成する必要があります。
f:id:ShunsukeKawai:20161201215715p:plain:w500

Xamarin.Forms プロジェクトに Mobile Engagement 接続(Android 編)

基本的に以下のチュートリアルに沿って進めていけば大丈夫です。(人任せ)
もう Mobile Engagement 作成は済んでいるので、キーをコピーするところ以降ぐらいから。
Xamarin.Android 用 Azure Mobile Engagement の使用

  • 【注意1】NuGet から Microsoft.Azure.Engagement.Xamarin をインストールしたら勝手に作られるフォルダの対処

NuGet からインストールすると勝手にフォルダが作られてそこに Mobile Engagement 用のリソースが置かれます。
(おそらく Xamarin.Android 用だからかな)
そのためその状態でビルドすると以下の様なエラーが発生します。
f:id:ShunsukeKawai:20161208150417p:plain
↓犯人
f:id:ShunsukeKawai:20161208150802p:plain
↓該当するフォルダに移動します。(+になってるヤツを移動した)
f:id:ShunsukeKawai:20161208150959p:plain

  • 【注意2】using は Microsoft.Azure.Engagement.Xamarin だけでいい

MainActivity.cs の using は一つだけで大丈夫です。

  • 【注意3】MainActivity.cs に EngagementActivity の継承をすることは Xamarin.Forms ではできない(FormsAppCompatActivity をすでに継承しているため)

こっち側の手順でやる必要がある
Azure Mobile Engagement Android SDK の詳細なレポート オプション

Xamarin.Forms プロジェクトに Mobile Engagement 接続(iOS 編)

基本的に以下のチュートリアルに沿って進めていけば大丈夫です。(人任せ2)
もう Mobile Engagement 作成は済んでいるので、キーをコピーするところ以降から。
Xamarin.iOS 用 Azure Mobile Engagement の使用

Xamarin.Forms プロジェクトに Mobile Engagement 接続(UWP 編)

基本的に以下のチュートリアルに沿って進めていけば大丈夫です。(人任せ3)
もう Mobile Engagement 作成は済んでいるので、キーをコピーするところ以降から。

Windows ユニバーサル アプリの Azure Mobile Engagement の概要

  • 【注意1】UWP は他と NuGet のパッケージが違うから注意(~Xamarin ではない)
  • 【注意2】リンク先にも書いてありますが、NuGet は、Windows 10 UWP アプリケーションで SDK のリソースを自動的にコピーしません。 NuGet パッケージのインストール時に表示される手順 (readme.txt) に従って、手動で行う必要があります。

とあります。readme.txtの内容は以下の通り。はい。自分で作れっつーことですね。

NuGet does not automatically copy the SDK resources in your Windows 10 UWP application. You have to do it manually until the scaffolding feature is reintroduced. 
Here are the steps for a new integration:

* Open your File Explorer.
* Navigate to the following location: %USERPROFILE%\.nuget\packages\MicrosoftAzure.MobileEngagement\3.4.1\content\win81
* Drag and drop the "Resources" folder from the file explorer to the root of your project in Visual Studio.
* In Visual Studio select your project and activate the "Show All files" icon on top of the "Solution Explorer".
* Some files may not be included in the project. To import them at once right click on the "Resources" folder, "Exclude from project" then another right click on the "Resources" folder, "Include in project" to re-include the whole folder. All files from the "Resources" folder are now included in your project.

まとめ

この辺までの基本的な設定 + PCL に interface 作って各プラットフォーム側で処理を追加して PCL の MainPage.xaml.cs にアクティブにする処理入れたヤツを以下のリポジトリにアップしてあります。
github.com

Mobile Engagement 側の接続文字列を設定すればとりあえず動くと思います。
Push 通知したい場合は各プラットフォームのリンク先を参考に設定してみてください。(最後も人任せ)

それではお疲れ様でした。