0条留言

带红点的图像-看起来很漂亮

一些应用程序需要相当多的内容,这些内容是相当静态的,但是会随着时间的推移而变化,然后,应用程序应该进行调整并向用户提供新的内容。假设我们想要一个为我们提供引号及其作者的应用程序。我们可以仅将引号添加到我们的应用程序中,但是每当要更新该应用程序时,我们都必须将我们的应用程序重新部署到商店。范围从不便到要求技术专家来更新应用程序,以简单地纠正诸如逗号之类的简单事物。因此很明显,在这些情况下,我们希望将内容与应用程序本身分开。

托管内容不需要在服务器上运行任何逻辑。除了简单的文件共享,我们不需要任何其他服务。关于安全性等可能还有一两个附加要求,但稍后会更多。而这正是 蔚蓝 Blob存储 可以为我们提供。

设置Blob存储

您需要具有一个Azure帐户才能创建Blob存储,因此您可以找到这些步骤。 这里。在Azure上,在容器下创建blob存储,如果尚未创建容器,则创建一个容器,然后将数据上传到该容器。在此示例中,我们将上传一个JSON文件。

显示带有一个JSON文件的Blobstorage容器

在实际的应用程序中,我们还可以提供多个其他文件,包括视频和其他静态文件。但是对于这个简单的演示,我们将坚持一个孤独的JSON文件。我们可以通过调用URL来访问内容:

样品获得邮递员的请求

在公共服务器上放置某些东西总是会引发有关安全性和种类的问题。因此,让我们来看看它们。

安全

因此,让我们从开箱即用的内容开始。最简单的安全性是您的数据是否公开。就像在网站上一样,但对于您的应用程序。默认情况下,您可以按以下方式限制对Blob存储的匿名访问:

  • 只读容器:允许所有人在容器级别进行读取,即“查看目录”并列出其中的所有blob。
  • 只读Blob:在此,调用方必须知道他要打开哪个Blob,并且仅限于读取Blob存储本身。
  • 匿名无读访问权限:这将仅对经过身份验证的各方进行访问。

在我们的示例中,我们将坚持使用匿名Blob访问。但是,如果您有兴趣添加一些额外的安全层,请务必查看 蔚蓝存储安全指南 解释了从共享密钥到使用Azure Active Directory(Azure AD)来保护数据访问的身份验证的不同方法。综上所述,这意味着Blob存储不仅从一开始就快速便捷,而且还可以进行修改以添加一些重要的保护层。

客户端

AppInAction

在客户端上,我们将要使用托管资源并在我们的应用程序中使用它。您可以在.Net Standard库中使用以下方法轻松完成此操作 JSON.Net 如下:

string quotesJson;
using (var httpClient = new HttpClient())
{
    var response = await httpClient.GetAsync("//gnabberonlinestorage.blob.core.windows.net/alpha/quotes.json");
    quotesJson = await response.Content.ReadAsStringAsync();
}
_quotes = JsonConvert.DeserializeObject<List<QuoteInfo>>(quotesJson);

While the above sample works and will get us our data it is not really smart, it will always pull the entire file even if nothing has changed. Fortunately, 蔚蓝 Blob存储 supports ETags which allows us to be smarter when creating the call, by adding the If-None-Match header to our request 如下:

public async Task Init()
{
    if (_quote != null) return;

    IsBusy = true;
    string quotesJson;
    using (var httpClient = new HttpClient())
    {
        if(!string.IsNullOrEmpty(CurrentEtagVersion)) httpClient.DefaultRequestHeaders.Add("If-None-Match", CurrentEtagVersion);
        var response = await httpClient.GetAsync("//gnabberonlinestorage.blob.core.windows.net/alpha/quotes.json");

        quotesJson = response.StatusCode == HttpStatusCode.NotModified
            ? ReadQuotesFromCache()
            : await response.Content.ReadAsStringAsync();

        UpdateLocalCache(response.Headers.ETag, quotesJson);
    }
    _quotes = JsonConvert.DeserializeObject<List<QuoteInfo>>(quotesJson);

    PickAndSetQuote();
    IsBusy = false;
}

如果本地和远程ETag匹配,则该呼叫将不会收到任何数据,从而使该呼叫的数据占用量很小。处理缓存的代码如下所示。请注意,为了访问首选项,使用了Xamarin.Essentials:

public string CurrentEtagVersion => Preferences.Get(EtagKey, string.Empty);

private void UpdateLocalCache(EntityTagHeaderValue eTag, string quotesJson)
{
    // Only update the cache if we need to
    if (eTag == null || CurrentEtagVersion == eTag.Tag) return;
    Preferences.Set(EtagKey, eTag.Tag);
    File.WriteAllText(_quotesFilename, quotesJson);
}

private string ReadQuotesFromCache()
{
    if (!File.Exists(_quotesFilename)) return string.Empty;
    return File.ReadAllText(_quotesFilename);
}

我将在此示例中保留它,但是由于我们已经将数据存储在本地缓存中,所以我们也可以考虑使此应用程序完全具有脱机功能。使用我们已经在使用的Xamarin Essentials,我们可以检查我们是否具有网络连接以及什么样的连接。此信息使我们可以决定是否要/可以访问远程存储,或者从初始缓存中加载数据。

您可以在以下位置找到整个客户端示例代码 的GitHub.

结论

在本文中,我们看到了如何使用Azure Blob存储作为后端服务来托管应用程序的内容,而无需实现任何Web服务器。您可以向存储添加安全层。后端的跟踪更改通过HTTP ETag开箱提供。

但是,这要花我多少钱?可能比您想象的要少,但请查看 斑点存储 定价以获得您的确切号码。

0条留言

When ever you want to display a list or collection of information under iOS tables are often the choice you will end up using. But what if you have content that changes and you want to update the data in a cell? Well that's what bindings are for right? But how do you use bindings in a UITableViewCell? Well let's check it out.

欢迎使用我们的倒计时应用程序。如您所见,它基本上是一个计时器列表,这些计时器显示在一个单元格中,并随着它们的滴答滴答而更新。

CellBinding

If we look at the basic setup of a displayed table, we will have UITableView which uses a UITableViewSource for managing the collection to be displayed and the rendering of the UITableViewCells. Then there is the view model which is providing a list of items, in our case timers, that should be displayed. So let's go through each of the items from View Model to Cell.

视图模型

MVVM灯 comes with helpers that allow you to convert an ObservableCollection to a UITableViewSource. Choosing this option will further ensure ensure the UI is updated when we add or remove an element to our collection. So let's take it:

public class MainViewModel : ViewModelBase
{
    public MainViewModel()
    {
        // ...
        Countdowns = new ObservableCollection<CountdownViewItem>();
        Countdowns.Add(new CountdownViewItem(new TimeSpan(0, 13, 37)));
    }

    // ...
    public ObservableCollection<CountdownViewItem> Countdowns { get; private set; }

    // ...
}

The CountDownViewItem hold's the state of the individual item. Hence the timer logic is embedded into it and will also implement the RaisePropertyChanged.

public class CountdownViewItem : ViewModelBase
{
    DateTime _expirationTimestamp;

    public CountdownViewItem(TimeSpan timespan)
    {
        if (timespan == null) throw new ArgumentNullException(nameof(timespan));
        _expirationTimestamp = DateTime.UtcNow + timespan;
        Countdown();
    }

    string _remainingTimeString;
    public string RemainingTimeString
    {
        get
        {
            return _remainingTimeString;
        }
        set
        {
            if (value == _remainingTimeString) return;
            _remainingTimeString = value;
            RaisePropertyChanged(nameof(RemainingTimeString));
        }
    }

    private async void Countdown()
    {
        while (DateTime.UtcNow < _expirationTimestamp)
        {
            TimeSpan remainingTime = _expirationTimestamp - DateTime.UtcNow;
            RemainingTimeString = remainingTime.ToString(@"hh\:mm\:ss");
            await Task.Delay(millisecondsDelay: 490);
        }

        RemainingTimeString = "Timer Expired";
    }
}

因此,让我们将这些视图模型用于UI中。

表格视图

One can choose to create the table either in a story board or in code. Choosing the code path we would have a UIViewController that creates the UITableView, set's the layout and creates the UITableSource from the view models collection:

class ViewController : UIViewController
{
    // private members

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        // Setup view and layouting

        // Setup bindings
        _tableViewController = Vm.Countdowns.GetController(CreatePersonCell, BindCellDelegate);
        _tableViewController.TableView = CountdownsTableView;

        AddTimerButton.SetCommand("TouchUpInside", Vm.AddCountdownCommand);
    }

    private void BindCellDelegate(UITableViewCell cell, CountdownViewItem countdownViewItem, NSIndexPath path)
    {
        var bindableCell = (CustomCell) cell;
        bindableCell.Configure(countdownViewItem);
    }

    private UITableViewCell CreatePersonCell(NSString cellIdentifier)
    {
        return CountdownsTableView.DequeueReusableCell(nameof(CustomCell));
    }
}

请注意,我们正在使用自定义单元格,并且正在将其注册到其中,以便我们可以重用该单元格。这将使我们的应用能够重复使用创建的单元格,即改善内存占用量,性能并被视为最佳实践。我们将很快讨论该单元的细节。

如果您正在寻找情节提要方法,请注意,此步骤仅此步骤会有所不同。看看 的GitHub 样本存储库。

细胞

The cell is invoked when via the BindCellDelegate method. So we can populate our cell with binding like this:

public class CustomCell : UITableViewCell
{
    CountdownViewItem _countdownViewItem;
    // ui label and constructors

    private void InitCell()
    {
        // Layout cell
    }

    public Binding<string, string> _timerBinding;

    internal void Configure(CountdownViewItem countdownViewItem)
    {
        _countdownViewItem = countdownViewItem;
        _timerBinding = this.SetBinding(() => _countdownViewItem.RemainingTimeString, () => RemainingTimeLabel.Text);
    }
}

将传入的视图项存储为方法中的成员变量,这一点非常重要。如果不这样做,则只会看到初始值,绑定将永远不会更新视图!

由于我们正在重用单元格,因此我们必须假设一个单元格可能已被上一项使用,并且可能存在现有绑定。

Let's remember that Bindings boil down to events. And when we register an event handler we should always ensure that we are not creating a memory leak I.e. that we deregister the event handler. MVVM灯 provides the method Detach which will deregister the event handler(s) behind the binding. Now why is this important? Well lists tend to come with multiple items. Having a memory leak for each item tends to be a bad design strategy to say the least. Therefor it is important to ensure that the binding in the cells are detached when it is no longer used.

This is also the reason why we are using a custom cell. It allows us to override the method PrepareForReuse which is invoked every time a cell is being reused. In this method we can restore the cell to it's initial state:

public override void PrepareForReuse()
{
    base.PrepareForReuse();
    _timerBinding?.Detach();
}

And that is how you can create bindings in a UITableViewCell.

结论

In this blog post we saw how we can create a binding for a UITableViewCell. We also covered how to avoid memory leaks when reusing cells (and you should generally reuse cells). Generally do not use bindings for static content in cells as they might impact your performance. When ever possible do not change the layout (constraints) due to a change in the content. Though not supported by the UITableViewSource created by MVVM灯 at the time of writing, try using different custom cells for different layouts.

您可以在以下位置找到整个应用示例 的GitHub.

0条留言

有没有想过其他应用程序是如何进行平滑屏幕过渡的?使用过渡确实可以使应用程序更加精致。但是,更重要的是,它在用户体验(UX)方面将为您的应用程序带来优势。

在这篇文章中,我们将实现以下过渡:

CustomTransition

在我们深入研究代码之前。请注意,每个转换都遵循以下步骤:

  • 在目标View Controller上配置过渡
  • 实施过渡代表
  • 实施过渡动画
  • 如果适用,请执行解雇过渡动画

您不必实施解雇过渡。但是,如果您导航回到原始页面。您确实应该这样做。

配置自定义过渡

题外话:社区尚未达成共识,最好的方法是编写UI。有些人喜欢用代码(例如我自己)来完成它,有些人则喜欢使用情节提要。因此,我们将仅介绍它们两者。好消息是,除了配置外,其余代码是相同的。

使用代码配置自定义过渡

iOS中的过渡实际上是在目标View Controller上定义的。在我们的示例中,我们将在用户选择按钮后立即导航到下一个View Controller:

_button.TouchUpInside += (e, s) =>
{
    var vc = new ModalViewController(this)
    {
        ModalPresentationStyle = UIModalPresentationStyle.Custom,
        TransitioningDelegate = new GrowTransitioningDelegate(_button)
    };

    NavigationController.PresentViewController(vc, true, null);
};

Note that we configure the ModalPresentationStyle and TransitionDelegate on the view controller that we are navigating to. The GrowTransitionDelegate takes the originating UIView. If you are not using Storyboards you can skip the section and dive right into how the GrowTransitioningDelegate is implemented.

使用情节提要板配置自定义过渡

如果您使用情节提要,您将很熟悉segue。要定义自定义过渡动画,您将必须按以下方式配置segue:

将情节提要剧板配置为使用“当前模式”,并将“显示和过渡”设置为默认。

Then in the originating view controller you can override the PrepareForSegue method:

public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
{
    base.PrepareForSegue(segue, sender);

    var destinationVC = segue.DestinationViewController as SecondViewController;
    destinationVC.Callee = this;
    destinationVC.TransitioningDelegate = new GrowTransitioningDelegate(sender as UIView);
    destinationVC.ModalPresentationStyle = UIModalPresentationStyle.Custom;
}

The target view controller in our case is name SecondViewController. It looks fairly similar to the event handler we defined before. On the destination view controller we set the TransitioningDelegate to GrowTransitioningDelegate which takes the originating UIView as constructor parameter. The only difference is that we do no longer pass the instance of the calling view controller as constructor parameter but use a property named Callee on the SecondViewController.

设置代表

The GrowTransitionDelegate inherits from UIViewControllerTransitioningDelegate from which we can override methods to add a custom animation for presenting 如下:

public class GrowTransitioningDelegate : UIViewControllerTransitioningDelegate
{
    readonly UIView _animationOrigin;

    public GrowTransitioningDelegate(UIView animationOrigin)
    {
        _animationOrigin = animationOrigin;
    }

    public override IUIViewControllerAnimatedTransitioning GetAnimationControllerForPresentedController(UIViewController presented, UIViewController presenting, UIViewController source)
    {
        var customTransition = new GrowTransitionAnimator(_animationOrigin);
        return customTransition;
    }
}

Let's follow along the scenario of the user navigating to another page. The GetAnimationControllerForPresentedController provides an animation implementation that inherits from IUIViewControllerAnimatedTransitioning. The originating UIView, a button in this example, is passed to the animation in the constructor. And that is all the transitioning delegate has to implement. So let's see how we implement the actual animation.

实施过渡动画

The animations are defined in a class that inherit from UIViewControllerAnimatedTransitioning. The following two methods have to implemented:

public class GrowTransitionAnimator : UIViewControllerAnimatedTransitioning
{
    readonly UIView _animationOrigin;

    public GrowTransitionAnimator(UIView animationOrigin)
    {
        _animationOrigin = animationOrigin;
    }

    public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
    {
        // The animation 
    }

    public override double TransitionDuration(IUIViewControllerContextTransitioning transitionContext)
    {
        return 0.3;
    }
}

The TransitionDuration method defines how long the animation will take. Usually this should be around 300 to 500 milliseconds. In the AnimateTransition method the actual transition and animation are defined.

iOS SDK提供了用于创建动画(例如过渡动画)的功能。使用这些将大大减少创建动画以定义UI对象的初始和所需目标状态所需的工作。动画的渲染由iOS在GPU上执行。因此,不仅因为您的应用看起来很棒,而且GPU会在早餐时吃转换,所以它也将变得活泼 Smile

The AnimateTransition method usually follows the following pattern:

  1. 获取您的源视图和目标视图控制器并查看
  2. 获取动画容器视图并添加目标
  3. 定义动画状态
    1. 目标视图的最终状态
    2. 目标视图的初始状态
  4. 执行动画并在完成时通知过渡上下文

因此,让我们逐步进行此步骤。首先,让我们获取源和目标视图控制器和视图:

public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
{
    // Get the from and to View Controllers and their views
    var fromVC = transitionContext.GetViewControllerForKey(UITransitionContext.FromViewControllerKey);
    var fromView = fromVC.View;
    
    var toVC = transitionContext.GetViewControllerForKey(UITransitionContext.ToViewControllerKey);
    var toView = toVC.View;
     
    // ...
}

然后获取动画容器并将目标添加到其中:

public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
{
    // ...

    // Add the to view to the transition container view
    var containerView = transitionContext.ContainerView;
    containerView.AddSubview(toView);
        
    // ...
}

现在来定义动画的部分。我们想在用户选择的按钮中间开始动画,即我们的动画类已经通过构造函数收到了该消息。

public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
{
    // ...

    // Set the desired target for the transition
    var appearedFrame = transitionContext.GetFinalFrameForViewController(toVC);
    
    // Set how the animation shall start
    var initialFrame = new CGRect(_animationOrigin.Frame.GetMidX(), _animationOrigin.Frame.GetMidY(), 0, 0);
    var finalFrame = appearedFrame;
    toView.Frame = initialFrame;
    
    // ...
}

现在剩下要做的就是执行动画并等待结果。然后将结果用于向过渡上下文发出动画已完成的信号:

public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
{
    // ...

    var isAnimationCompleted = await UIView.AnimateAsync(TransitionDuration(transitionContext), () => {
        toView.Frame = finalFrame;
    });
    
    transitionContext.CompleteTransition(isAnimationCompleted);
}

我们的过渡动画完成了。如果您要实现其他动画。例如,动画从一个角落开始。更改动画的原点将为您提供所需的效果。

副作用 While animations are great and you will perhaps notice that some UI elements are drawn early and then move into position during the transition. This can feel slightly off. So while working with animations it can make sense to move the layout code of the UI components to the ViewDidAppear method.

创建关闭动画

For having the inverse animation for dismissing the view. First the GetAnimationControllerForDismissedController method has to be overwritten in the delegate class (same one as before):

public class GrowTransitioningDelegate : UIViewControllerTransitioningDelegate
{
    readonly UIView _animationOrigin;

    public GrowTransitioningDelegate(UIView animationOrigin)
    {
        _animationOrigin = animationOrigin;
    }

    public override IUIViewControllerAnimatedTransitioning GetAnimationControllerForPresentedController(UIViewController presented, UIViewController presenting, UIViewController source)
    {
        // ...
    }

    public override IUIViewControllerAnimatedTransitioning GetAnimationControllerForDismissedController(UIViewController dismissed)
    {
        var customTransition = new ShrinkTransitionAnimator(_animationOrigin);
        return customTransition;
    }
}

Then create an implementation which inherits UIViewControllerAnimatedTransitioning and has the desired reverse animation.

public class ShrinkTransitionAnimator : UIViewControllerAnimatedTransitioning
{
    readonly UIView _animationOrigin;

    public ShrinkTransitionAnimator(UIView animationTarget)
    {
        _animationOrigin = animationTarget;
    }

    public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
    {
        // Get the from and to View Controllers and their views
        var fromVC = transitionContext.GetViewControllerForKey(UITransitionContext.FromViewControllerKey);
        var fromView = fromVC.View;

        var toVC = transitionContext.GetViewControllerForKey(UITransitionContext.ToViewControllerKey);
        var toView = toVC.View;

        // Add the to view to the transition container view
        var containerView = transitionContext.ContainerView;

        // Set the desired target for the transition
        var appearedFrame = transitionContext.GetFinalFrameForViewController(fromVC);

        // Set how the animation shall end
        var finalFrame = new CGRect(_animationOrigin.Frame.GetMidX(), _animationOrigin.Frame.GetMidY(), 0, 0);
        fromView.Frame = appearedFrame;

        var isAnimationCompleted = await UIView.AnimateAsync(TransitionDuration(transitionContext), () => {
            fromView.Frame = finalFrame;
        });

        fromView.RemoveFromSuperview();

        transitionContext.CompleteTransition(isAnimationCompleted);
    }

    public override double TransitionDuration(IUIViewControllerContextTransitioning transitionContext)
    {
        return 0.3;
    }
}

尽管看起来很相似,但仍有一些要点。首先,这次的起点是我们要关闭的视图控制器。由于它已经是“视图”树的一部分,因此不再需要添加。相反,一旦动画完成,您实际上将要删除它。

结论

In this post we went through the steps required to create a custom transition. Further we looked at how we can implement the dismiss animation. The steps can be reused for different kind of animations which are defined in the implementation of the UIViewControllerAnimatedTransitioning class.

您可以在以下位置找到完整的代码示例 的GitHub.

0条留言

刚刚在最近的尝试中偶然发现了这个。 iOS的默认行为是使用前一个导航控制器的标题作为后退按钮的文本。

BackButtonNotSet

如果您想要不同的文字(例如后退),则可能会找到此文字 食谱 来自Xamarin的作品。但这确实像是一种黑客,它要求您在每种视图中都采用这种方法。

Another approach is to set the NavigationItem.BackBarButtonItem attribute in the view controller:

NavigationItem.BackBarButtonItem = new UIBarButtonItem {Title = "Back"};

设置的文本将显示为 下一页 视图控制器。换句话说,上一个视图控制器始终设置以下内容的后退按钮文本。

BackButtonSet

如果您使用中心点来生成视图,例如导航服务或工厂,可以将其应用于您创建的每个视图。这使得这种方法更容易出错。

如果使用情节提要,则还有其他选择。标题可以直接在设计器中的“导航项”,“后退按钮”下设置。

在情节提要编辑器中设置后退按钮标题

请注意,仅当您选择(不再建议)推送导航用于您的搜索时,此选项才可用。

结论

在这篇文章中,我们看到了如何设置后退按钮的标题。后退按钮的标题始终在前面的视图控制器中设置。

使用情节提要,可以直接在情节提要设计器中设置标题。

您可以在上找到整个小应用程序示例 的GitHub.

0条留言

十字路口的交通信号灯

在这篇文章中,我们着眼于当用户选择向后导航时得到通知。如果您必须控制iOS下的导航,我强烈建议您使用模式导航签出。但是,如果您只是想跟踪导航何时单击后退按钮,该怎么办。或使用滑动手势返回上一页。

虽然没有直接事件,但是有几种方法可以实现。

自定义NavigationController

One way is to subclass the UINavigationController:

public class AwareNavigationController : UINavigationController
{
    public event EventHandler PoppedViewController;

    public AwareNavigationController() : base() { }
    public AwareNavigationController(UIViewController rootViewController) : base(rootViewController) { }
    public AwareNavigationController(IntPtr intPtr) : base(intPtr) { }
    public AwareNavigationController(NSCoder coder) : base(coder) { }
    public AwareNavigationController(NSObjectFlag t) : base(t) { }
    public AwareNavigationController(string nibName, NSBundle bundle) : base(nibName, bundle) { }
    public AwareNavigationController(Type navigationBarType, Type toolbarType) : base(navigationBarType, toolbarType) { }

    public override UIViewController PopViewController(bool animated)
    {
        PoppedViewController?.Invoke(this, null);
        return base.PopViewController(animated);
    }
}

然后,您可以开始使用新的导航控制器:

new AwareNavigationController(new ContainterViewController());

并关注事件:

public override void ViewWillAppear(bool animated)
{
    base.ViewWillAppear(animated);
    ((AwareNavigationController)NavigationController).PoppedViewController += ViewControllerPopped;
}


private void ViewControllerPopped(object sender, EventArgs e)
{
    Console.WriteLine("Going back Shell");
}

如果要在单个位置注册导航并将此信息作为事件甚至消息提供,请使用此方法。如果您要编写导航服务,则此方法很可能是您的最佳选择。

使用此方法时有几点注意事项。确保注销事件处理程序。您将需要在ViewWillDisappear方法中注销事件处理程序。如果您稍后尝试这样做,即ViewDidDisappear导航控制器引用将已经为null。

WillMoveToParentViewController

Alternatively to creating a custom navigation view controller you can override the following method in your ViewController:

public override void WillMoveToParentViewController(UIViewController parent)
{
    base.WillMoveToParentViewController(parent);
    if(parent == null) Console.WriteLine("Going back Shell");
}

如果传入的父视图控制器为null,则将从视图堆栈中删除该视图。请注意,如果您有子视图控制器,则必须扩展上述方法,否则不会通知子视图:

public override void WillMoveToParentViewController(UIViewController parent)
{
    base.WillMoveToParentViewController(parent);

    if (parent == null)
    {
        var childVCs = ChildViewControllers;
        foreach(var childVC in childVCs)
        {
            childVC.RemoveFromParentViewController();
        }

        Console.WriteLine("Method override: Going back Shell");
    }
}

I would recommend this approach when you are only need to detect the navigation in one or two ViewController's I.e. but do not require this event globally in the app.

不推荐的方法

If you have stumbled over the suggestion to simply check the attribute IsMovingFromParentViewController in the ViewWillDisappear / ViewDidDisappear, be aware that this is not recommended. While it might work at first, as soon as you need to detect the back navigation in a child view controller this attribute will always be set to true. Even if navigating to another view controller and not backwards.

public override void ViewDidDisappear(bool animated)
{
    base.ViewWillDisappear(animated);
    // not recommended!
    if(IsMovingFromParentViewController) Console.WriteLine("Going back Shell");
}

Since one can’t forbid a ViewController to be used as child view controller, I would strongly recommend to stay away from this approach.

结论

在这篇文章中,已经显示了通知正在进行的向后导航的不同方法。如果必须阻止用户向后导航,我强烈建议您首先使用模式导航。

在上找到一个小样本应用程序 的GitHub.

知道这里没有提到的另一个选择吗?或对方法有任何想法。通过发表评论让我知道。