またteratailネタなんですが、調べたり考えたりするのに面白いネタだと思い、週末少しいろいろやってみた結果を残しておこうかと思います。
https://teratail.com/questions/55706
質問者さんのポイントはいくつかあって
- iOSだとNavigationPageの戻るボタンのイベントが取得できない (AndroidだとNavigationPage#OnBackButtonPressedをオーバーライドすれば可能)
- 押されたタイミングでイベントをフックして進捗表示し、処理後に前画面へ戻りたい
- このためOnDisappearingなどでは処理できない
ということでした。
一応実現はできました。
ただ、いろいろ調べたり考えた結果
「実装自体は可能だけど、やるべきではないケースが多そう」
という結論になりました。
以降は調査と考察の内容になります。
最初に結論
さて、上記の課題はぼちぼちメジャーな話のようで、結論から言うとリンク先の方法で対処は可能でした。
実際に実装してみたコードがこちらになります。
https://github.com/nuitsjp/XamarinSamples/tree/master/OnBackButtonPressed
考察
さて、これをやろうとした場合の最大の問題は
「スワイプによるナヴィゲーションバックを殺してやらなくてはならない」
という点にあります。
ユーザビリティに制約を設けてまで本当にやる必要があるのか?という点をよく検討する必要があるでしょう。
実装方法
とはいえ、せっかく調べたし、実現方法の解説は残しておきたいと思います。
今回の対応をするためには2つの対応が必要です。
- NavigationRendererをカスタマイズしてスワイプバックのジェスチャーを殺す
- PageRendererをカスタマイズして、戻るボタン押下時にVMに通知してあげる
の2つの対応になります。
NavigationRendererをカスタマイズしてスワイプバックのジェスチャーを殺す
以下がそのコードです。
iOSプロジェクトに作成してあげる必要があります。
[assembly: ExportRenderer(typeof(NavigationPage), typeof(CustomNavigationRenderer))] namespace OnBackButtonPressed.iOS { public class CustomNavigationRenderer : NavigationRenderer { public override void SetViewControllers(UIViewController[] controllers, bool animated) { base.SetViewControllers(controllers, animated); foreach (var controller in controllers) { // Disable swipe back ((UINavigationController)controller).InteractivePopGestureRecognizer.Enabled = false; } } public override void ViewDidLoad() { base.ViewDidLoad(); if (InteractivePopGestureRecognizer != null) { InteractivePopGestureRecognizer.Enabled = false; } } } }
PageRendererをカスタマイズして、戻るボタン押下時にVMに通知してあげる
まずはVMで通知を受け取るためのインターフェースを作成します。
これはPCLのプロジェクトへ作成しましょう。
public interface IConfirmGoBack { Task<bool> CanGoBackAsync(); }
そして、PageRendererを継承して拡張します。
これはiOSプロジェクト側に作成します。
[assembly: ExportRenderer(typeof(Page), typeof(CustomPageRenderer))] namespace OnBackButtonPressed.iOS { public class CustomPageRenderer : PageRenderer { public override void ViewWillAppear(bool animated) { base.ViewWillAppear(animated); var page = Element as Page; var navigationPage = page.Parent as NavigationPage; var root = this.NavigationController.TopViewController; root.NavigationItem.SetLeftBarButtonItem(new UIBarButtonItem($"< Back", UIBarButtonItemStyle.Plain, async (sender, args) => { var navPage = page.Parent as NavigationPage; var vm = page.BindingContext as IConfirmGoBack; if (vm != null) { if (await vm.CanGoBackAsync()) navPage.PopAsync(); } else navPage.PopAsync(); }), true); } } }
これでOKです。
戻るボタンのイベントを受けたいVewModelでIConfirmGoBackインターフェースを実装してあげればイベントをフックできます。
こんな感じです。
public class SecondPageViewModel : IConfirmGoBack, INotifyPropertyChanged { private bool _isProcessing; public bool IsProcessing { get { return _isProcessing; } set { _isProcessing = value; OnPropertyChanged(); } } public async Task<bool> CanGoBackAsync() { IsProcessing = true; try { await Task.Delay(2000); return true; } finally { IsProcessing = false; } } }
falseを返してあげれば戻るを止めることも可能です。
という訳で、実現自体は可能でしたが、前述の通り、UIに制約ができてしまうことや、そもそも使いやすいのか?それ?疑惑もあるため、用法容量を守ってご利用ください。
という訳で、今日はここまで。
それではまた!