Как обрабатывать/отменять навигацию в форматах Xamarin

Я попытался использовать обратную навигацию, переопределив OnBackButtonPressed, но каким-то образом ее вообще не вызывали. Я использую ContentPage и самую последнюю версию 1.4.2.

Ответ 1

Хорошо, после многих часов я понял это. Есть три части.

# 1 Обращение с аппаратной кнопкой на Android. Это легко, переопределить OnBackButtonPressed. Помните, что это только для кнопки назад и Android. Он не будет обрабатывать кнопку панели навигации. Как вы можете видеть, я пытался вернуться через браузер, прежде чем отступать от страницы, но вы можете поставить любую логику, в которой вы нуждаетесь.

  protected override bool OnBackButtonPressed()
    {
        if (_browser.CanGoBack)
        {
            _browser.GoBack();
            return true;
        }
        else
        {
            //await Navigation.PopAsync(true);
            base.OnBackButtonPressed();
            return true;
        }
    }

# 2 навигационная кнопка iOS. Это было очень сложно, если вы посмотрите вокруг Интернета, вы найдете пару примеров замены кнопки "назад" новой пользовательской кнопкой, но почти невозможно заставить ее выглядеть как ваши другие страницы. В этом случае я сделал прозрачную кнопку, которая находится поверх обычной кнопки.

[assembly: ExportRenderer(typeof(MyAdvantagePage), typeof

(MyAdvantagePageRenderer))]
namespace Advantage.MyAdvantage.MobileApp.iOS.Renderers
{
    public class MyAdvantagePageRenderer : Xamarin.Forms.Platform.iOS.PageRenderer
    {
        public override void ViewWillAppear(bool animated)
        {
            base.ViewWillAppear(animated);

            if (((MyAdvantagePage)Element).EnableBackButtonOverride)
            {
                SetCustomBackButton();
            }
        }
        private void SetCustomBackButton()
        {
            UIButton btn = new UIButton();
            btn.Frame = new CGRect(0, 0, 50, 40);
            btn.BackgroundColor = UIColor.Clear;

            btn.TouchDown += (sender, e) =>
            {
                // Whatever your custom back button click handling
                if (((MyAdvantagePage)Element)?.
                CustomBackButtonAction != null)
                {
                    ((MyAdvantagePage)Element)?.
                       CustomBackButtonAction.Invoke();
                }
            };
            NavigationController.NavigationBar.AddSubview(btn);
        }
    }
}

Android, сложно. В более ранних версиях и будущих версиях Форм после исправления вы можете просто переопределить OnOptionsItemselected, подобный этому

       public override bool OnOptionsItemSelected(IMenuItem item)
    {
        // check if the current item id 
        // is equals to the back button id
        if (item.ItemId == 16908332)
        {
            // retrieve the current xamarin forms page instance
            var currentpage = (MyAdvantagePage)
            Xamarin.Forms.Application.
            Current.MainPage.Navigation.
            NavigationStack.LastOrDefault();

            // check if the page has subscribed to 
            // the custom back button event
            if (currentpage?.CustomBackButtonAction != null)
            {
                // invoke the Custom back button action
                currentpage?.CustomBackButtonAction.Invoke();
                // and disable the default back button action
                return false;
            }

            // if its not subscribed then go ahead 
            // with the default back button action
            return base.OnOptionsItemSelected(item);
        }
        else
        {
            // since its not the back button 
            //click, pass the event to the base
            return base.OnOptionsItemSelected(item);
        }
    }

Однако, если вы используете FormsAppCompatActivity, вам нужно добавить в свой OnCreate в MainActivity это, чтобы установить панель инструментов:

Android.Support.V7.Widget.Toolbar toolbar = this.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
            SetSupportActionBar(toolbar);

Но подождите! Если у вас слишком старая версия .Forms или слишком новая версия, появляется ошибка, когда панель инструментов имеет значение null. Если это произойдет, взломанный вместе способ, которым я получил работу, чтобы сделать крайний срок, выглядит следующим образом. В OnCreate в MainActivity:

        MobileApp.Pages.Articles.ArticleDetail.androdAction = () =>
        {
            Android.Support.V7.Widget.Toolbar toolbar = this.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
            SetSupportActionBar(toolbar);
        };

ArticleDetail - это страница, androidAction - это Action, который я запускаю на OnAppearing, если платформа Android на моей странице. К этому моменту вашего приложения панель инструментов больше не будет равна нулю.

Еще несколько шагов, рендеринг iOS, который мы сделали выше, использует свойства, которые необходимо добавить на любую страницу, на которую вы создаете рендеринг. Я делал это для моего класса MyAdvantagePage, который я создал, который реализует ContentPage. Поэтому в классе MyAdvantagePage я добавил

public Action CustomBackButtonAction { get; set; }

        public static readonly BindableProperty EnableBackButtonOverrideProperty =
               BindableProperty.Create(
               nameof(EnableBackButtonOverride),
               typeof(bool),
               typeof(MyAdvantagePage),
               false);

        /// <summary>
        /// Gets or Sets Custom Back button overriding state
        /// </summary>
        public bool EnableBackButtonOverride
        {
            get
            {
                return (bool)GetValue(EnableBackButtonOverrideProperty);
            }
            set
            {
                SetValue(EnableBackButtonOverrideProperty, value);
            }
        }

Теперь, когда это все сделано, на любом из моих MyAdvantagePage я могу добавить это

:


 this.EnableBackButtonOverride = true;
            this.CustomBackButtonAction = async () =>
            {
                if (_browser.CanGoBack)
                {
                    _browser.GoBack();
                }
                else
                {
                    await Navigation.PopAsync(true);
                }
            };

Это должно быть все, чтобы заставить его работать на аппаратном обеспечении Android, и переходить обратно как к Android, так и к iOS.

Ответ 2

Вы правы, в классе страницы переопределите OnBackButtonPressed и верните true, если вы хотите предотвратить навигацию. Он отлично работает для меня, и у меня такая же версия.

protected override bool OnBackButtonPressed()
{
    if (Condition)
        return true;
    return base.OnBackButtonPressed();
}

Ответ 3

В зависимости от того, что именно вы ищете (я бы не рекомендовал использовать это, если вы просто хотите отменить навигацию по кнопке "Назад" ), OnDisappearing может быть другой опцией:

protected override void OnDisappearing()
{
       //back button logic here
}

Ответ 4

OnBackButtonPressed() будет вызываться, когда нажата кнопка возврата оборудования, как в android. Это не будет работать на кнопку возврата программного обеспечения, как в ios.

Ответ 5

Для всех, кто по-прежнему борется с этой проблемой - в основном вы не можете перехватить обратную навигационную кросс-платформу. Сказав, что есть два подхода, которые эффективно решают проблему:

  • Спрячьте кнопку назад на странице NavigationPage с помощью NavigationPage.ShowHasBackButton(this, false) и нажимайте страницу модальности с пользовательской кнопкой Back/Cancel/Close

  • Перехватите обратную навигацию изначально для каждой платформы. Это хорошая статья, которая делает это для iOS и Android: https://theconfuzedsourcecode.wordpress.com/2017/03/12/lets-override-navigation-bar-back-button-click-in-xamarin-forms/

Для UWP вы сами:)

Edit:

Ну, уже не так, как я это сделал:) На самом деле это оказалось довольно просто - есть только одна кнопка "назад" и поддерживается формами, поэтому вам просто нужно переопределить ContentPages OnBackButtonPressed:

    protected override bool OnBackButtonPressed()
    {
        if (Device.RuntimePlatform.Equals(Device.UWP))
        {
            OnClosePageRequested();
            return true;
        }
        else
        {
            base.OnBackButtonPressed();
            return false;
        }
    }

    async void OnClosePageRequested()
    {
        var tdvm = (TaskDetailsViewModel)BindingContext;
        if (tdvm.CanSaveTask())
        {
            var result = await DisplayAlert("Wait", "You have unsaved changes! Are you sure you want to go back?", "Discard changes", "Cancel");

            if (result)
            {
                tdvm.DiscardChanges();
                await Navigation.PopAsync(true);
            }
        }
        else
        {
            await Navigation.PopAsync(true);
        }           
    }

Ответ 6

Хитрость заключается в том, чтобы реализовать собственную навигационную страницу, которая наследуется от NavigationPage. У него есть соответствующие события Pushed, Popped и PoppedToRoot.

Пример реализации может выглядеть следующим образом:

public class PageLifetimeSupportingNavigationPage : NavigationPage
{
    public PageLifetimeSupportingNavigationPage(Page content)
        : base(content)
    {
        Init();
    }

    private void Init()
    {
        Pushed += (sender, e) => OpenPage(e.Page);

        Popped += (sender, e) => ClosePage(e.Page);

        PoppedToRoot += (sender, e) =>
        {
            var args = e as PoppedToRootEventArgs;
            if (args == null)
                return;

            foreach (var page in args.PoppedPages.Reverse())
                ClosePage(page);
        };
    }

    private static void OpenPage(Page page)
    {
        if (page is IPageLifetime navpage)
            navpage.OnOpening();
    }

    private static void ClosePage(Page page)
    {
        if (page is IPageLifetime navpage)
            navpage.OnClosed();

        page.BindingContext = null;
    }
}

Страницы реализуют следующий интерфейс:

public interface IPageLifetime
{
    void OnOpening();
    void OnClosed();
}

Этот интерфейс может быть реализован в базовом классе для всех страниц, а затем делегировать ему вызовы модели представления.

Страница навигации и может быть создана следующим образом:

var navigationPage = new PageLifetimeSupportingNavigationPage(new MainPage());

MainPage будет корневой страницей для отображения.

Конечно, вы также можете просто использовать NavigationPage и подписаться на нее, не наследуя ее.

Ответ 7

 protected override bool OnBackButtonPressed()
        {
            base.OnBackButtonPressed();
                return true;
        }

base.OnBackButtonPressed() возвращает false при нажатии кнопки возврата оборудования. Для того, чтобы предотвратить работу кнопки "Назад" или перейти на предыдущую страницу. переопределяющая функция должна быть возвращена как true. При возврате true он остается на текущей странице формы xamarin, и состояние страницы также сохраняется.

Ответ 8

Может быть, это может быть полезно, вам нужно спрятать кнопку "Назад", а затем заменить на свою собственную кнопку:

public static UIViewController AddBackButton(this UIViewController controller, EventHandler ev){
    controller.NavigationItem.HidesBackButton = true;
    var btn = new UIBarButtonItem(UIImage.FromFile("myIcon.png"), UIBarButtonItemStyle.Plain, ev);
    UIBarButtonItem[] items = new[] { btn };
    controller.NavigationItem.LeftBarButtonItems = items;
    return controller;
}

public static UIViewController DeleteBack(this UIViewController controller)
{
    controller.NavigationItem.LeftBarButtonItems = null;
    return controller;
}

Затем вызовите их в эти методы:

public override void ViewWillAppear(bool animated)
{
    base.ViewWillAppear(animated);
    this.AddBackButton(DoSomething);
    UpdateFrames();
}

public override void ViewWillDisappear(Boolean animated)
{
    this.DeleteBackButton();
}

public void DoSomething(object sender, EventArgs e)
{            
    //Do a barrel roll
}

Ответ 9

Другой способ - использовать Rg.Plugins.Popup, который позволяет вам реализовать хорошее всплывающее окно. Он использует другой NavigationStack => Rg.Plugins.Popup.Services.PopupNavigation.Instance.PopupStack. Таким образом, ваша страница не будет обтекать панель навигации.

В вашем случае я бы просто

  • Создать всплывающее окно с непрозрачным фоном
  • Переопределить "OnBackButtonPressed для Android" на "ParentPage" примерно так:

    protected override bool OnBackButtonPressed() { return Rg.Plugins.Popup.Services.PopupNavigation.Instance.PopupStack.Any(); }

Так как кнопка "Назад" влияет на обычный NavigationStack, ваш родитель будет всплывать всякий раз, когда пользователь пытается использовать его во время "всплывающего окна".

Что теперь? Xaml все, что вы хотите, чтобы правильно закрыть всплывающее окно со всеми проверками, которые вы хотите.

💥 Проблема решена для этих целей💥

  • [x] Android
  • [x] iOS
  • [-] Windows Phone (устарел. Используйте v1.1.0-pre5, если требуется WP)
  • [x] UWP (мин. задание: 10.0.16299)