Could not save account to keychain: -34018

今まで動いてたKeyChainにデータ入れようとする部分が突然
"Could not save account to keychain: -34018"
と吐き始めたので何だとググったんですが、

xamarin.ios - Xamarin Auth Store Keychain not working after ios10 upgrade - Stack Overflow

のようにOSのバージョンによるものですかね。ついさっきシミュレータのターゲット7PlusのiOS10.2にしたところでした。

プロジェクトのプロパティ->iOS Bundle SigningのCustom
 entitlementsにプロジェクト直下にあるEntitlements.plistを指定したらエラー出なくなりました。

上のリンクだと最後の方に空のEntitlements.plistを指定すればいいよ!と言ってるんですが、そもEntitlements.plistがないこととかあるんですかね?

Xamarin.Formsでアプリリリースしてみてあれこれ

序文

こちらはXamarin Advent Calendar 2016 - Qiita20日目の記事です。
今年はかかわっている会社の方でHS証券様の取引ツールのスマホアプリをXamarin.Formsで作成させていただいてリリースしましたのでそのあたりの話を詳しく。というかほかにXamarinがらみのことやってないので話すネタがないとも言います。
今回作ったのは株式売買などの証券取引などを行う証券会社のお客様が使うアプリです。証券会社のお客様が株を買ったりドルの値段を見たり、自分の取引口座の持ってる株を見たり、銀行との入出金などを行えるものです。
このアプリはパッケージとして横展開をしていく予定で、パッケージとしての共通部分とそれを利用する会社ごとのコードが組み合わさって一つのアプリとなるようにしています。
App.xamlなどで定義した色やスタイルなどを変えたり独自のビューを差し込んだりしてその会社のアプリとして特徴が出るようにする予定です。

アプリ全体の構成

全体の構成としては以下のような感じになります。
図の左側はプロジェクトの構成、右側は各々のプロジェクトの中で実装され動いてるもののイメージです。

会社別のプロジェクトと共通プロジェクト群が一つのアプリを構成します。なるべく多くの部分を共通部分にあげ、その他独自の部分を会社ごとのプロジェクトに残していきます。

プロジェクト構成としては以下のような感じになります。

  • VMから上のロジック層やサービス層。F#PCL。部分によって5,6個に分かれてるけど割愛。
  • 各社共通UI部、共通UI寄りスタートアップロジックなど。C#。OS共通部はPCL、OS個別部は各OS用のライブラリプロジェクト。
  • 各社個別部。アプリ本体、各社非共通UI、ロジックなど。C#。Xamarin.Formsテンプレで作るPCLと各OSのプロジェクト。

内部の構造としては上の方から

  • サービスとして動くロジック。単独もしくは他のサービスと連携して動く。基本的にアクターとして動いてる。
  • 各画面のVM部。ものによってはVMとModelとを分けたりしてますが、わけなくてもよさげなものはファットVMとしてる。異論は認める。
  • UI関係を取りまとめるViewManagerとそのHandlerとしてのTabbedPage継承クラスなど含むUIフレーム部。その上に乗っかるView部。ViewはVMをもとに生成されNavigationやPopやOverwrapなどとして表示される。
  • サービスの開始や初期ビューの表示などを行うスタートアップ部。サスペンド・最下位処理などもここの予定(未実装)。

開発の進め方

スマホ側の開発体制は以下のような感じでした。

  • 自分:全体のフレームワークなどと発注系周り。F#、C#、XMAL。
  • 開発者A:その他のビューの多くの原型。F#、C#、XMAL。
  • 開発者B:後からAが作った原型を引継ぐ。F#はまだ不慣れかも。F#の調整とC#、XMAL。
  • 開発者C:主にアイコンやXAMLなどのレイアウトを担当。少しC#
  • そのほか:プロダクトオーナー兼マネージャーやテスターさんなど。

このほかにサーバー側実装部隊などがいます。

開発にあたっては次のように進めました。

  • 先に検証を兼ねて全体的なフレームの原型を自分が作って想定した構造で動くミニマムなものを作成。その上にサービスやビューを個々の開発者が増築。
  • 開発においては基本的に個々のVMとViewの組み合わせ、それと関連するサービスだけを考慮すればいい。サービス間の連携も初めから大体こんなもんだろうと決めてあったので個々の開発者が作ったものの調整はあまりなかった。
  • サービスやVMととりあえず表示して機能するViewを個々の開発者が作成、大体動いたらCに回し画面をきれいにしてもらうなどとして分担。

各社向けに並行して開発・修正・リリースなどをしていくのでGitでブランチを分けつつ、適当なタイミングで別の会社向けにいじったものなどを統合してパッケージ部分の進化と個々の会社の部分の開発と仕様変更を行っていってます。今のところそれほど困ってないですがこのパッケージを使ってくれる会社様が増えてきた場合はもう少し検討が必要かもです。

UIフレーム

UIフレーム部分だけ切り取るとこんな感じに。

UIのフレームとしてはVM側で次の画面に遷移したいときに、次のVMを作成してナビゲーションプッシュとして遷移したい、ポップアップ表示したい、オーバーラップで半透明な表示したい、今の画面を閉じてあるタブを開きその中のある画面が表示されるようにしたいなどを行えるようにしています。それをうけてビュー側でそのメッセージに合うような表示をし、そのほか画面ごとの回転制御やサスペンド・レジュームなどをおこないます。

UI表示などのフレームとしてPrismなど人気だと思いますが、作成してた頃はPrismまだ出来立てほやほやだったり要件に合わないところもあるだろうし、前にMvvmCrossを使って起動速度改善とかいろいろこうしたいというときにどうにもならなかったりでフレームワークを使うことに懲りたので自前で作りました。面倒なところはありましたが、自前で作ったおかげでいろいろとゴニョゴニョできるので変な要件が来ても困らないというメリットがが。
ビューの表示の仕方で最悪ゴリゴリXamairn.FormsでいうViewの部分をアニメーション絡めて表示してタブを切り替えたりナビゲーションしたりポップアップしてる風に見える仕組みを作れば何かトラブっても逃げられると思っていて、結果想定通りにそれで逃げることになりました。

今回の要件の中で、

  • 半透明表示
  • 発注時などに上からピロッと出る通知ビュー
  • どの画面よりも前にポップアップとしてでないといけないビュー
  • タブビューに表示されるものとその他に押し込まれるものの表示ビューのカスタマイズ
  • VMの取り扱いたい方法にあったサスペンドリジュームの実装
  • Android側、Xamarin.FormsのTabbedPageが要件に合わなかったので自分で全部描画する方法に変更

などのあたりは自前フレームワーク作ったので割とはまることなく対処できました。フレームワーク使ってたらこういうことしたいけれどやり方が分からないということが多く困ったのではないかと思います。けどこの辺は自前実装するコストと便利さの兼ね合いでしょうから便利なPrismを選んだ方が適切な場合も多々あると思います。

UIフレーム動作

VM側で処理に応じて次のVMを作成、表示の仕方含むViewMessageとして投げます。
コードとしては次のような感じです。

//SettingVMを作成、それをNavigationPushとして表示するCommand
let _settingCmd=toEverCmd(fun _->postVMsg<|VMShowAsNavPush (SettingVM()))

//今Overwrapとして表示してるViewを閉じたら作成した発注パネルVMPopupとして表示する関数
let _toOrderPanel(cmdy, side, cMrgn) = 
    postVMsg <| VMCloseOverwrapFromContentThen (VMShowAsPopup(OrderPanelsUtil.toOrderPanelVM cmdy ticket side cMrgn None))

このようなメッセージをViewManager側で受け取りVMに応じたViewを作成、それをNavigationやPopup、Overwrapなどの表示方法に応じた形で表示します。
ViewManagerはメッセージを受けてIViewHandlerを実装したクラスに具体的に表示命令を出します。
最初はTabbedPageを継承したクラスがそれを担ってNavigationPageのPushAsyncやNavigation.PushModalAsyncなどで表示などしていましたが、先にも述べたようにAndroid側はAppCompat絡めると要件に合わないところが出てきたので全部自前で表示する方法に変更しました。
具体的にはTabのような表示もNavigationPush、Popup、Overwrapな表示も全部PageViewの上のGridの上で適当にアニメーションを絡めて表示します。この辺はもっとAndroidっぽく表示しろなどの要件などがなかったのでできたところかもですが。
こんな感じに動いてます。

コード量とプラットフォームごとの共有率

JXUGC #17 お前の Xamarin アプリを見せてみろ! - connpassで話した証券取引アプリとNote app作ってみたからの抜粋ですが、

という感じで93%ほど共有しています。F#はコード密度が高いから実質共有率はもっと高いんだと補正しなくてもよく共有できてるのではと思っています。

苦労したところ

まぁXamarin.Formsでの書き方わからなかったりバグがあったりなど色々はまってた気がしますが、次の辺りは割と大きめにはまって一時期はこれはやばいと思ったことがが。

  • 上に述べたようにFormsのAndoroidのTabbedPageが要件に合わない。Androidよく知らないしForum見てもなんか根深そうで解決できなそう\(^o^)/オワタ->自前描画に書き換え。
  • Androidのパフォーマンスが遅い。なんか全体がもっさりしてるからこっちで何かやっても仕方なさそう。起動が糞遅い。\(^o^)/オワタ->AOTをかけたらなんか速くなったお!->SkiaSharpのDLLがAOT出来ない。エラーになる\(^o^)/オワタ->のでSkiaSharp使ってた部分をAndroid.Graphicsで書き直ししてAOTかけた。
  • メモリ沢山食いすぎ。そもそもXamarin.Formsのアプリ自体メモリ食ってる。メモリリークの原因潰したいけどProfilerがバージョンが上がらずなんか使えない。出力ファイルを解析できるMonoのHeapShotのライブラリを利用して何が参照をつかんでしまってるか見るミニツール作る->Profilerのバージョンアップでファイルフォーマットが変えられたので動かなくなる\(^o^)/オワタ->HeapShotフォークしてCのソースのコメント見ながらエスパーしつつ新しいフォーマット見れるように改造、解析ツール使い続ける⊂(^ω^)⊃ セフセフ!!->Profiler1.0になったけどちらっと試したら参照元たどるの実装されたらしいんだけどどう使ったらいいのかわからない。またフォーマット変えられてないか試してない<−今ここ。
  • ボタンが押されたらその場所その時に応じたピッカーを表示という要件が、Xamarin.Formsのピッカーでやろうとしたけれどどうしてもうまく動かず。なんかゴニョゴニョしたら動いたっぽいのでそのまま触らんとことしてたけどリリース直前になってまた変な挙動し始める\(^o^)/オワタ->最後はXamarin.Formsのソース見てレンダラーなどでごりっと書いてあっさり動く。ソースあるのだから最初からそうしてればよかった。

まとめ

こうして振り返ってみるといろいろあったような気もしますが、今はのど元過ぎてすべてが懐かしい…
結論としてはJXUG17でみなXamarin.Forms使ってたようにXamarin.Formsは使えるいい子なのでみんな使うといいよという感じです。
XamarinもXamarin.FormsもF#もいいぞと(´・ω・`)

話は全くそれますが、途中のポンチ絵はJXUG17でXamarinじゃないストアアプリを見せてチートに一位をもらった時のNoteアプリで作ってます。
ストアアプリ8.1で作ってますが使ってみたいという奇特な方いましたら@omanukeまで連絡ください。Dropboxのリンクを送るのでその中のPowerShellスクリプトをたたいてもらえれば動かせると思います。
自分は慣れてて自然回避してましたがあっちこっちバグあったり怪しい挙動をする代物ですが…出来る出来る詐欺のクラウド経由の同期機能も近々動きそうな気がしないでもないです。
よろしくお願いします。

Xamarin.iOSでシミュレータデバッグや実機デプロイできないときに効いたやつ

こちら43239 – Unable to debug on on real on device
の最後にあるVSを再起動しつつWindowsの%AppData%\Local\Xamarin\MonoTouch\の下とMacの$HOME/Library/Caches/Xamarin/XMAの下を削除する方法でできなかった奴ができる場面が昨日今日と多々ありました。

ついに銀の弾丸を得たのか俺はと思いましたが、そのあと何度かやってもできない解決できないパターンが出てきてXamarin沼の底の深さを再認識しています…誰か助けて…(´・ω・`)

Xamarin バッドノウハウ大全 の補足

はじめに

先日のわんくま横浜で田淵さんの話された Xamarinバッドノウハウ大全 で自分の報告したバグ?が多く採用されてたようなので補足です。

一応自分の立ち位置を言っておくと、Xamarinのトラブルとかバグとかに対してタヒね!と怨嗟の声を日々ツイッターでつぶやいてますが、Xamarinはほかのものに代えがたくXamarin.Formsもとても有効な弾丸だと思ってます。
一言でいうと Xamarin、Xamarin.Formsはいいぞ。F#もいいぞ。 という感じです。
前置きは置いておいて以下補足。

Visual Studio と Xamarin Studio で相互運用す ると .csproj がゴリっと書き換わったりすることがあ るみたいよ。

WindowsのVSとMacのXSでソースをやり取りするとIDEのバージョン?とかなんかいろいろ書き換わります。
それで問題あるかどうかですが、自分のソースではF#部分が昔は全く通らなかったり、この間でもコンパイル通るようになったけど実行すると一発で落ちるなどで実際の相互運用は出来ていないので不明です。
ただその辺の書き換えのせいでVSに戻したらコンパイル通らなくなって、もう二度とするまいと思ったことがあったような気がが。

Xamarin.Forms の Slider は Min を先に指定し ないと落ちるみたい…

最近でどうなってるか試してませんが、前はそれですっ飛んでかつデバッグウィンドウにも有用な情報が出ず少しはまりました。
今はXAMLでのバインディングをせずにC#でViewなどのOnBindingContextにてMinimumにDouble.MinValue、MaximumにDouble.MaxValueをセットしたのちに値セットなりバインディングをしています。

Xamarin.Forms で PushModalAsync 表示をし たときに、Navigation.ModalStack.Count の値が iOSAndroidで違うんだって。

Navigate.PushModalAsyncなどするとNavigation.ModalStackに積まれていくんですが、一枚PushModalAsyncしたときにNavigation.ModalStack.Countの値がAndroidでは2、iOSでは1になります。
何かしようとしたらすぐ気づくと思いますが、最初はあれ?ってなると思います。

Xamarin.Forms の Device.BeginInvokeOnMainThread は、 Android ではそのまま実行されるけど iOS はポス トされてから実行されるので挙動が変わるんだって。

Device.BeginInvokeOnMainThread的なものの使い方として

  • 1.別スレッドからUIスレッドで処理したいものの実行をする。
  • 2.今UIスレッドにいて、今の処理が終わった後で実行してほしいのでPostしておく 

というのが多いのではと思います。
iOSはそのような挙動をするのですが、Androidでは今UIスレッドにいる場合はポストされて後で実行するのではなくそのまま実行してしまいます。
async/awaitでの処理待ちなど絡んだ時に場合によって予期せぬデッドロックを生む可能性もあると思うので注意が必要かと。

Task.Run(()=>Device.BeginInvokeOnMainThread(...));

などとすればAndroidでもUIスレッド上でPostする的な挙動になると思います。

Xamarin.Forms の StackLayout は設定がややこ しいよ。特に XXXandExpand が闇みたいでレイア ウトもバグるんだって。

Xamarin.FormsのStackLayoutはXXXAndExpandというオプションがありGridでの*のような感じで残りを占めるという風にレイアウトできます。
複数個Viewを配置した後、残りをこのViewで占めるなどGridレイアウトでもよく行うと思いますが、GridではRowDefinitionsなどであらかじめ配置する数を決めておかないといけなかったりそもそもRowDefinitionsの定義をしなければいけないなど面倒で、StackLayoutなら思いつくまま追加した後、広げるものにXXXAndExpand等をつければよく追加も簡単なので最初は多用していました。
けれどいろいろとレイアウトしてるうちに何かのタイミングでレイアウトがずれることが多発してどうやらStackLayoutのレイアウトが怪しいのではという結論に至りました。
そもそもXXXAndExpandはおそらく初見では絶対にどうなるか理解できずに間違えると思います。
その点Gridは面倒だけれど挙動が予測できます。
結局自分は怪しいところは全部Gridに戻しました。

Xamarin.Forms の TapGestureRecognizer の 判定範囲が Android 側で広すぎて、スワイプと かぶっておかしくなるらしいよ。

これは自前Carouselを実装してた時だと思いますが、横にスワイプをするもの上にTapGestureRecognizerを実装したViewを配置した時に、Android側でタップにはならないだろうと思うぐらい横に動かしてから指を話すとTapが発火してしまってました。
結局TapGestureRecognizerではやり方が分からなかったのでRendererでAndroidのTouchイベントを拾って自分で動かした範囲を拾って判定するようにしました。
この辺のXamarin.FormsのジェスチャーAndroid,iOSのTouchイベントがScrollViewなどの上にあるビューでどう発火するかなどはプラットフォームごとにだいぶパターンが違うので、もし何か作る場合には調べる必要があります。

Xamarin.Forms の PanGestureRecognizer は iOSAndroid でイベントが発火したり発火しな かったりするんだって。

これがまさにそんな感じでScrollViewの上に配置したViewでPanGestureをとろうとするとAndroidでは発火しなかったのかScrollViewでの期待した動作にならなかったのかいい感じにならず、iOSでは発火して望みの挙動になりました。結局Android側はTouchイベントで処理するようにしてiOS側はそのままPanGestureを使いました。
たしかXamarin.FormsのPanGestureRecognizerとiOSのUIPanGestureRecognizerではまた挙動がいろいろ違った気がします。
逆にあるものがだめでもほかのものでは望みの挙動をするなどの場合もあるのでいろいろ試してみればいいと思います。

Xamarin.Forms で InputTransparent=false にし ても iOSAndroid で下のコントロールにイベント を透過できないことがあるらしいよ。

これはちょっと逆でViewの上に半透明なビューを置いたときに、下のView上にあるボタンが発火しないようにしたくて上の半透明のViewのInputTransparent=falseで行けるのかと思ったのですが、だめで下のボタンが押されてしまったという話です。
これもiOSかAndoirdか、どっちかは下のものにイベントがいかないようになったんですが、もう一方がそうならないという感じだったはず。
結局下にイベントを行かせたくないViewにTapイベントを何も処理しないダミーのTapGestureRecognizerを配置することで下に行かないようになりました。

Xamarin.Forms の Picker も闇らしいよ…

これは話がややこしいんですが、Xamarin.FormsのPickerはちゃんと動きます。
けれどXamarin.FormsのPickerはEntryのような見た目で、今回ほしかったものはボタンの形状をしていて文字列として現在の値がある、ボタンを押すとその時に選択候補を取得し、それをピッカーとして表示して選択するというものでした。
ボタンを押したときに選択候補を取得する必要がなければ、ボタンの上に透明に近いPicker(Opacity=0.011.たしか0.01だとiOSでおかしかったはず)を配置することで触った瞬間にピッカーが出るという風にできました。
がどうしてもボタンを押して選択候補を取得する必要があったのでPickerのWidth/HeightRequestを1に、透明近くにしたうえで上に書いたダミーのTapGestureRecognizerを配置して触られないようにする、ボタンが押されたときに選択候補を取得して、Pickerに値などをセット、その後PickerにFocusをするとピッカーが出るのでこれでいけるかなど色々試行錯誤してました。
が、これで動きそうだとおもったのですが、何かのタイミングでボタンを押してもピッカーが表示されない現象が起き、どうにも解決できず。
ので最終時にボタンを押したときのイベントでネイティブ側で各々のプラットフォームのやり方でピッカーを表示するようにしたらあっさりと動きました。思えば最初からそうしてればよかったと思いますがAndroidをあまり知らなかったのでFormsで何とかしようと思ったのが間違いのもとでした。
困ったらネイティブ処理。大事なのでもう一度言うと困ったらネイティブ処理です(´・ω・`)

Xamarin.Forms の ListView はメモリリークするん だって…

これは自分じゃないかも?けどなんかProfilerで調べたりしてるとリークはしてそうな気はしますが、自分の場合だとそこまでそこで困ってはないです。今回はビューごと破棄することが多いのでその辺で解消されてるのかもしれません。
解決法?CashingStrategyを変えるとかですかね…あとどうしても残ってしまうものはそのListViewがいらなくなった後Cellの中のViewのBindingContextにnullを入れてBindingを断ち切る、ViewCellの上に配置したViewを何かのタイミングで切り離して破棄するなども有効そうな予感です。

Android の View は Mono 側で GC してもしば らく Java 側から握られてたりしてメモリから消える までに時間が掛かるんだって。メモリリークじゃない らしいよ。

えーとMonoのGCをした後もJava側からXamarin.FormsのViewへの参照が残ってていろいろ解放されないなどとなるためメモリリークの調査時は注意が必要です。
Javaの方のGCもやると消えるんですが、自分の経験的には今参照残ってないはずのものをメモリからパージしたい場合にはMonoのGCJavaGCを交互に3回ずつぐらいやった方が確実に消えると思います。
なおしばらく放置してればJavaのほうがつかんでるものも消えます。メモリの使い方にもよると思いますが、2-3分放置してたら消えたことが多かったような。
それとは別になんかメモリに少し残るなぁというものはそれはそれであった気がしますが詳細未確認。
しかしプロファイラーは早く良くなりませんかね…JetBrainが作ってくれてもいいんですよ(/ω・\)チラッ

iOSデバッグできる端末と launch failed でデ バッグできない端末が未だにあるんだって。

これはおそらくXamarinのせいではなく自分の方のiPhone端末のProfileがおかしいせいです。がそれでデプロイできなかったりデプロイはできてもデバッグ実行できなかったりするんですが、その辺の原因がよくわからないのはXamarinのせいじゃないかなー。Xamarin側でもAppleがろくなエラーを出さずに何ともしようがないのかもしれませんが。
いずれにしても何がおかしいのかわからないので放置してデバッグできる端末やシミュレータでしのいでます。
まぁこの辺は去年、おととしに比べたら楽なもんですよえぇ。

※朝起きたら思い出したので追記

F#のsprintfで引数を5個以上にすると実機にデプロイした時だけそこですっ飛ぶ。

これはバグジラにも前から上がってるし治ったよとなってるはずですが、この間もなりましたね。このバグの時はデバッグウィンドウに痕跡を残さずすっ飛ばれるので原因解析にちょと困ります。

解決法…引数を5個以上にしない。

SkiaSharpで文字列のMeasureが変なサイズを返す。

SkiaSharp速いしいいんですが、文字列の表示と合わないサイズを返してきて困ります。解決法ないんですが、ひとつ前のバージョンでだいぶ良くなりました。最新のは組み込んではまだ試してませんが期待。

SkiaSharpを使うとAndroid側のAOTができない。

Xamarin.Forms、なにやらAndroid側のパフォーマンスがよろしくないんですが、AOTすると起動速度やその後の処理速度が改善されます。ものにもよると思いますが、体感できるレベルで速くなるかと。
が、SkiaSharpを組み込むとAndoird側でAOT時に怒られてできなくなります。
バグジラにも上がってて対処中のようですが、予定は未定の予感…

まとめ

とりあえず覚えていたもの羅列しましたが、のど元過ぎたら忘れてる感じでほんとはもっといろいろあったような…
けれどどんな開発環境でもなんだかんだいろいろトラブルあるはずだし、XamarinもXamarin.Formsもバグらなければ大変安定して動きます。
いずれにしてもXamarin.FormsはなんだかんだAndroidのことをほとんど知らないような自分でもクロスプラットフォームな開発ができる使える弾丸です。
基本はVMより上のロジックはすべてのプラットフォームで共通化し、多くのUIもXamarin.Formsで共有化する。
どうしても足りない部分やネイティブで作った方が早そうな部分はRendererやネイティブで実装すればいいだけです。
フレームワークは結局便利な部分とフレームワークでカバーできてないところ隠蔽されてどうにもならないところの利点と欠点を併せ持ってると思いますが、Xamarin.Formsはフレームワークに合わないところはいろいろとごにょれるフックがあるのでだいぶバランスもよく多くの場面で問題に対処できるのではないでしょうか。
自分は次の開発でもおそらくXamarin.Formsを使うと思います。

この補足が皆さんのとりあえずのXamarin沼への一歩の一助になれば幸いです。
多分底なし沼だと思いますが、皆で沈めば怖くない(´・ω・`)

改めて

 Xamarin、Xamarin.Formsはいいぞ。F#もいいぞ。 

Xamarin.FormsのiOSでEntryがKeyboardで隠れないようにするBehavior

Androidはよしなに隠れないようにしてくれてるみたいですが、iOSはやっぱり隠れてしまうので隠れないようにするBehavior作りました。
ほんとは

NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillShowNotification, n =>
{
    var h = ((NSValue)n.UserInfo[UIKeyboard.FrameBeginUserInfoKey]).CGRectValue.Height;
    var v =((NSNumber) n.UserInfo[UIKeyboard.AnimationDurationUserInfoKey]).DoubleValue;

    …
}

などで高さやアニメーションのデュレーションとってやらないとだめだと思いますが、それだとBehaviorにしにくいじゃないですかーということでマジックナンバーで手抜き。

          <Entry Text="{Binding Pass}"
             HorizontalOptions="FillAndExpand">
            <Entry.Behaviors>
              <appCommon:NoHiddenBehavior RootView="{x:Reference rootGrid}"/>
            </Entry.Behaviors>
          </Entry>

などとすれば使えると思います。rootGridはそのページの一番上のViewです。ContentViewでもいいのではと思いますが、その下のを今回は指定してます。

以下Behaviorのソース。OnDetachingFromも手抜いてます。

    public class NoHiddenBehavior : Behavior<Entry>
    {
        public View RootView { get; set; }
        public double Margin { get; set; }
        public uint AnimateDuration { get; set; }
        public double KeyboardHeight { get; set; }

        public NoHiddenBehavior()
        {
            Margin = 10;
            AnimateDuration = 200;
            KeyboardHeight = 216;
        }
        protected override void OnAttachedTo(Entry entry)
        {
            entry.Focused += (sender, e) =>
             {
                 if (Device.OS == TargetPlatform.iOS)
                 {
                     var y = RelatedY(RootView,entry);
                     var scroll = RootView.Height - y - entry.Height-Margin - KeyboardHeight;
                     if(scroll<0)
                        RootView.TranslateTo(0, scroll, AnimateDuration);
                 }
             };
            entry.Unfocused += (sender, e) =>
            {
                if (Device.OS == TargetPlatform.iOS)
                {
                    RootView.TranslateTo(0, 0, AnimateDuration);
                }
            };
        }

        protected override void OnDetachingFrom(Entry entry)
        {
        }
        double RelatedY(View prmParent, View button)
        {
            var y = button.Y;
            var parent = (VisualElement)button.Parent;
            while (parent != prmParent)
            {
                y += parent.Y;
                parent = (VisualElement)parent.Parent;
            }
            return y;
        }
    }

※追記
ScrollViewの中にあるもののことなどは何も考えてないのでご注意ください。

Xamarin.Formsで突然のCould not load file or assembly 'System.Runtime.Extensions'

昨日Debugビルドしていろいろ動作を確認て出来たと思い、Releaseビルドしてざっと確認後配布したら今日テストの方からある画面立ち上がんねーぞゴラァとお叱りをうけました(実際はもっと丁寧な言葉遣いです)。

なにやら

[06:47:13.126]:LTERR:VMToView:ex raised.vm is HogeVM ex=System.IO.FileNotFoundException: Could not load file or assembly 'System.Runtime.Extensions' or one of its dependencies
File name: 'System.Runtime.Extensions'
  at System.AppDomain.Load (System.Reflection.AssemblyName assemblyRef, System.Security.Policy.Evidence assemblySecurity) <0x10042ce50 + 0x003ec> in <filename unknown>:0 
  at System.AppDomain.Load (System.Reflection.AssemblyName assemblyRef) <0x10042cd70 + 0x0001f> in <filename unknown>:0 
  at System.Reflection.Assembly.Load (System.Reflection.AssemblyName assemblyRef) <0x100455790 + 0x00023> in <filename unknown>:0 
  at Xamarin.Forms.Xaml.XamlParser.GetElementType (Xamarin.Forms.Xaml.XmlType xmlType, IXmlLineInfo xmlInfo, System.Reflection.Assembly currentAssembly, Xamarin.Forms.Xaml.XamlParseException& exception) <0x100ead3e0 + 0x003c7> in <filename unknown>:0 
  at Xamarin.Forms.Xaml.Internals.XamlTypeResolver.Resolve (System.String qualifiedTypeName, IServiceProvider serviceProvider, Xamarin.Forms.Xaml.XamlParseException& exception) <0x100eb2280 + 0x00323> in <filename unknown>:0 
  at Xamarin.Forms.Xaml.Internals.XamlTypeResolver.Xamarin.Forms.Xaml.IXamlTypeResolver.Resolve (System.String qualifiedTypeName, IServiceProvider serviceProvider) <0x100eb21c0 + 0x0002f> in <filename unknown>:0 
  at Xamarin.Forms.Xaml.StaticExtension.ProvideValue (IServiceProvider serviceProvider) <0x100ea18a0 + 0x002ff> in <filename unknown>:0 
  at HogeView.InitializeComponent () <0x10233cc00 + 0x0d7bf> in <filename unknown>:0 

などとエラーが出てる模様。
これだけだと皆目見当つかないのでXAMLのContentViewの中身をまるっとコメントアウトして画面が出るのを確認、少しずつコメントアウトを外していったところ、どうやら次の箇所の模様。

<Label.FormattedText>
  <FormattedString>
    <Span Text="Hoge"/>
    <Span Text="{x:Static system:Environment.NewLine}" />
    <Span Text="Fuga" />
  </FormattedString>
</Label.FormattedText>

Labelで改行して表示したく、それをXAMLで行うにはどうしたものかとググって出てきてこれで勝つると思いデバッグ実行で確認してよしと思ってましたが、Releaseだと上のようなエラーをはいていたようです。

結局次のような感じにしてReleaseビルドでも動くようになりました。

namespace AppCommon
{
    public static class Constants
    {
        public static readonly string HogeFuga = "Hoge\r\nFuga";
    }
}
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
              xmlns:appCommon="clr-namespace:AppCommon;assembly=AppCommon">
    <Label Text="{x:Static appCommon:Constants.HogeFuga}">
</ContentView>

みんな配る前にはちゃんと配るもので動作確認しようね。お兄さんとの約束だぞ(´・ω・`)
しかし俺たちのXamarin沼との戦いはまだこれからだぜ…(未完)

追記

<Label Text="Hoge&#xa;Fuga"/>

でいいと教えていただきますた(´・ω・`)

Xamarin.Profilerでメモリリーク解析2(Android)

※[悲報]
下記のスクリプト、Xamarin Profilerの0.32以前でないと動かないようです…
そのうち直すかもですが、元のライブラリーが対応してくれないと難しいかもです…(;´・ω・)


Xamarin.Profilerでメモリリーク解析(Android) - omanuke-ekunamoの日記の続きでSnapshot間のオブジェクトの増減を見れるようにしました。

のような場合でSnapshot8と12の間の増減を見たい場合、

dumpDiff<|heapDiff 8 12

とすると

========= Heap Diff 8 to 12 =================================
[13807] AppCommon.Controls.CustomNavBtnItem  3
[791] Java.Interop.JniPeerMembers.JniInstanceFields  3
[790] Java.Interop.JniPeerMembers.JniInstanceMethods  3
[793] Java.Interop.JniPeerMembers.JniStaticFields  3
[792] Java.Interop.JniPeerMembers.JniStaticMethods  3
[445] Java.Interop.JniType  3
[20868] Microsoft.FSharp.Core.PrintfImpl.Final1@224<Microsoft.FSharp.Core.Unit,System.String,System.String,System.Int32>  3
[2687] System.Action  3
[337] System.AsyncCallback  3
[588] System.Collections.Concurrent.ConcurrentDictionary.Node<System.IntPtr,System.IDisposable>  3
[4567] System.Collections.Generic.Dictionary.Entry<System.String,System.Object>[]  3
[843] System.Collections.Generic.Dictionary<System.Type,Java.Interop.JniPeerMembers.JniInstanceMethods>  3
[13118] System.Collections.Generic.List<Xamarin.Forms.Behavior>  3
[13136] System.Collections.Generic.List<Xamarin.Forms.TriggerBase>  3
[696] System.Collections.Hashtable  3
[3604] System.Collections.Hashtable.bucket[]  3
[13108] System.Collections.ObjectModel.ObservableCollection.SimpleMonitor<Xamarin.Forms.Behavior>  3
[13114] System.Collections.ObjectModel.ObservableCollection.SimpleMonitor<Xamarin.Forms.TriggerBase>  3
[13780] System.Net.Sockets.NetworkStream  3
[2929] System.Random  3
[2922] System.Threading.IThreadPoolWorkItem[]  3
[2908] System.Threading.ThreadPoolWorkQueue.WorkStealingQueue  3
[2928] System.Threading.ThreadPoolWorkQueueThreadLocals  3
[20843] System.Tuple<Microsoft.FSharp.Core.FSharpFunc<Microsoft.FSharp.Core.FSharpFunc<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.PrintfImpl.PrintfEnv<Microsoft.FSharp.Core.Unit,System.String,System.String>>,Microsoft.FSharp.Core.FSharpFunc<System.Int32,System.String>>,System.Int32>  3
[20365] TAC.Model.JsonParseUtil.JsonParser<TAC.Model.CommonType.PosSum[]>  3
[21334] Trading.Chart.BaseTypes.RendererPriority  3
[23710] Trading.Chart.Processor.CalcType  3
[25283] Trading.Chart.Processor.ProcessDataType  3
[20879] Trading.Chart.Proto.ViewType  3
[21224] Trading.Chart.ViewDataState  3
[13101] Xamarin.Forms.AttachedCollection<Xamarin.Forms.Behavior>  3
[13111] Xamarin.Forms.AttachedCollection<Xamarin.Forms.TriggerBase>  3
[12896] Xamarin.Forms.IGestureRecognizer[]  3
[13214] Xamarin.Forms.Image  3
[13211] Xamarin.Forms.TapGestureRecognizer  3
[13833] Android.Views.GestureDetector  4
[1939] Android.Views.MotionEvent  4
[20855] Microsoft.FSharp.Core.PrintfImpl.basicNumberToString@584-220  4
[17874] System.Func<System.Int32,System.Boolean>  4
[17903] System.Lazy.Boxed<Android.Views.GestureDetector>  4
[5131] TAC.Model.JsonParseUtil.JsonParser<System.Boolean>  4
[5491] TAC.Model.TLineMode  4
[14779] Xamarin.Forms.Platform.Android.ImageRenderer  4
[20752] Microsoft.FSharp.Collections.MapTree.MapOne<System.String,Microsoft.FSharp.Collections.FSharpList<System.Tuple<System.String,System.String,Microsoft.FSharp.Collections.FSharpList<System.Tuple<System.String,System.Int32>>>>>  5
[24947] Microsoft.FSharp.Core.PrintfImpl.Final1@224<Microsoft.FSharp.Core.Unit,System.String,System.String,System.Int64>  5

などと[typeId] 型名 増減 という感じで表示します。
怪しい参照などあったら

dumpAllPathByType (hShot 12) 21212

などとみたいSnapshotとtypeIdを指定すると被参照パスみれます。

スクリプトこちら(´・ω・`)

#I @"D:\GitHub\heap-shot\HeapShot.Reader\obj\Debug"
//#I @"d:\Dropbox"
#r "heapshot.reader.dll"

open HeapShot.Reader
open System.IO

let path= @"D:\GitHub\heap-shot\HeapShot"
//let path= @"D:\Dropbox"
let fName=Path.Combine(path,"r_.mlpd")
let omap=new ObjectMapReader(fName)
omap.Read()

let ls=omap.LastSnapshot

let hShot i=omap.HeapShots.[i]
//dummy listener
let listener={new IProgressListener with
                member this.ReportProgress(msg,progress)=
                  printfn"[progress] %s %f" msg progress
                member this.Cancelled=false}
//node in PathTree->o and name
let nodeToO (pTree:PathTree) node=
  let o=pTree.GetNodeObject node
  let name=ls.GetObjectTypeName o
  o,name  
//dump each reference path
let rec dumpPath (pTree:PathTree) depth maxDepth node=
  let tabs="".PadLeft(depth*4)
  let o,name=nodeToO pTree node
  printfn "%s%s(%d)" tabs name o
  if depth<maxDepth then
    pTree.GetChildNodes node
    |>Seq.iter(fun cNode->dumpPath pTree (depth+1) maxDepth cNode)
//dump all reference path
let dumpAllPath (pTree:PathTree) maxDepth=
  pTree.GetRootNodes()
  |>Seq.iter(fun rNode->
    let o,name=nodeToO pTree rNode
    printfn"----[%x] %s ------" o name
    dumpPath pTree 0 maxDepth rNode)
//dump all reference path for type
let dumpAllPathByType (ls:HeapSnapshot) t=
  let pTree=ls.GetRoots(listener,t)
  //dump all with max depth
  dumpAllPath pTree 100
//type,name,object count from type name     
let printTnc (t,n,c)=printfn"[%d] %s  %d" t n c
let dumpTncs tncs=tncs|>Seq.iter printTnc

let tncByType (ls:HeapSnapshot) t=
  let n=ls.GetTypeName t
  let os=ls.GetObjectsByType  t
  let c=os|>Seq.length
  t,n,c
let toAllTncs (ls:HeapSnapshot)=
  ls.GetTypes()
  |>Seq.map (tncByType ls)
  |>Seq.filter(fun(_,_,c)->c>0)
  |>Seq.sortBy(fun(_,n,_)->n)
let tncsByKey ls key=
  toAllTncs ls|>Seq.filter(fun(_,n,_)->n.Contains(key))

//diff
let tncsDiff tncs1 tncs2=
  let rec loop acc tncs1 tncs2=
    match (tncs1,tncs2) with
    |[],[]->acc|>List.rev
    |[],(t,n,c)::ts->loop ((t,n,c)::acc) [] ts
    |(t,n,c)::ts,[]->loop ((t,n,-c)::acc) ts []
    |(t1,n1,c1)::ts1,(t2,n2,c2)::ts2->
      let c=Microsoft.FSharp.Core.Operators.compare n1 n2
      if c=0 then loop((t2,n2,c2-c1)::acc) ts1 ts2
      elif c<0 then loop((t1,n1,-c1)::acc) ts1 tncs2
      else loop((t2,n2,c2)::acc) tncs1 ts2
  loop [] (List.ofSeq tncs1) (List.ofSeq tncs2)
let heapDiff fromI toI=
  printfn"========= Heap Diff %d to %d =================================" fromI toI
  let tncs i=hShot i|>toAllTncs
  tncsDiff (tncs fromI) (tncs toI)
let dumpDiff diff=
  diff
  |>List.filter(fun(_,_,diff)->diff<>0)
  |>List.sortBy(fun(_,_,d)->d)
  |>List.iter printTnc

let dumpTncByType (ls:HeapSnapshot) t=[tncByType ls t]|>dumpTncs


omap.HeapShots.Count


dumpDiff<|heapDiff 8 12
dumpDiff<|heapDiff 3 7

dumpAllPathByType (hShot 23) 21212