private void DidShowViewController(UINavigationController navigationController, UIViewController viewController, bool animated) { TraceViewControllers(nameof(DidShowViewController), viewController); if (!(viewController is PageViewController pageViewController)) { // When the ViewController isn't a PageViewController, it means it doesn't have anything to do with the Frame. // It's possibly a modal ViewController. // We just ignore it. return; } var lastRequest = pageViewController.AssociatedRequests.LastOrDefault(); if (lastRequest != null) { // Mark the request as handled by the NavigationController. lastRequest.WasHandledByController = true; if (lastRequest == _controllerToFrameRequest) { _frameToControllerRequests.Remove(_controllerToFrameRequest); _controllerToFrameRequest = null; } else { if (TryGetLast(_frameToControllerRequests, out var frameRequest)) { if (NavigationRequest.Correlates(lastRequest, frameRequest)) { // Now that the NavigationController handled the frame request, we remove it from the list. _frameToControllerRequests.RemoveLast(); } else { // Note for the future: We might be able to improve this by reseting the Frame to the NavigationController's content. // However, this case doesn't seem to really happen. // Something bad happened. We clear the request queue to try to recover. _frameToControllerRequests.Clear(); this.Log().Error($"Can't process DidShowViewController because the last request doesn't match the current request."); } } else { // It's possible that the NavigationController is the source of this event. When that's the case, the list of Frame requests is possibly empty. } } } else { this.Log().Error($"Can't process DidShowViewController because the current PageViewController's AssociatedRequests list is empty."); } }
/// <summary> /// This is called on <see cref="Frame.Navigating"/>. /// We use this handler to cancel the navigation when the request conflicts with the <see cref="NavigationController"/>. /// </summary> private void OnFrameNavigating(object sender, NavigatingCancelEventArgs e) { if (e.Cancel) { // If something cancelled the navigation, we simply ignore the event. return; } var frameRequest = new NavigationRequest(_frame, e); if (_controllerToFrameRequest != null) { // We get here when the UINavigationController initiated a navigation (like a back swipe) that is being executed by the Frame. if (NavigationRequest.Correlates(frameRequest, _controllerToFrameRequest)) { // We queue the request so that we can handle it in OnFrameNavigated and ignore it in OnFrameBackStackChanged. _frameToControllerRequests.AddFirst(_controllerToFrameRequest); } else { // When the Frame's request doesn't matche the UINavigationController's request. We cancel the Frame's request. // Ex: The UINavigationController is doing a native back, but the Frame wants to go forward. // This sequencing can happen when you press back during an ViewModel operation that usually ends with a navigation. e.Cancel = true; if (this.Log().IsEnabled(LogLevel.Debug)) { this.Log().Debug("Cancelled frame navigating request because a native navigation is in progress."); } } } else { // We queue the request so that we can handle it in OnFrameNavigated and ignore it in OnFrameBackStackChanged. _frameToControllerRequests.AddFirst(frameRequest); } }
/// <summary> /// This is called on <see cref="Frame.NavigationStopped"/>. /// We use this handler to remove requests cancelled by <see cref="NavigatingCancelEventArgs.Cancel"/>. /// </summary> private void OnFrameNavigationStopped(object sender, NavigationEventArgs e) { var request = new NavigationRequest(_frame, e); if (TryGetFirst(_frameToControllerRequests, out var frameToControllerRequest) && NavigationRequest.Correlates(request, frameToControllerRequest)) { if (this.Log().IsEnabled(LogLevel.Debug)) { this.Log().Debug("Aborted navigation request because the Frame.Navigating event was cancelled."); } _frameToControllerRequests.RemoveFirst(); } else { // We shouldn't get here because the frame events are synchronous. this.Log().Error($"Can't process OnFrameNavigationStopped because the request in queue doesn't match the current request."); } }
/// <summary> /// This is called on <see cref="Frame.Navigated"/>. /// We use this handler to create requets for the <see cref="NavigationController"/>. /// </summary> private void OnFrameNavigated(object sender, NavigationEventArgs e) { // We create a request object from the current state. We only use this object to correlate it with existing requests. var request = new NavigationRequest(_frame, e); if (TryGetFirst(_frameToControllerRequests, out var frameRequest) && NavigationRequest.Correlates(request, frameRequest)) { // Mark the request as handled by the frame because we're in the Navigated handler. frameRequest.WasHandledByFrame = true; if (frameRequest == _controllerToFrameRequest) { // If the request is the one created by the NavigationController, we don't have to do anything at this point. // The DidShowViewController method will simply remove it from the list once it gets called. } else { // Get the page from the event args. var page = e.Content as Page; // Use that page to get the native ViewController. var viewController = page.FindViewController() ?? new PageViewController(page); // If that ViewController is a PageViewController, we add the request to its list. (viewController as PageViewController)?.AssociatedRequests.Add(frameRequest); // We get the isAnimated flag from the transition info. var isAnimated = GetIsAnimated(frameRequest.TransitionInfo); switch (frameRequest.NavigationMode) { case NavigationMode.Back: if (this.Log().IsEnabled(LogLevel.Debug)) { this.Log().Debug("Poping ViewController to replicate Frame's back navigation."); } NavigationController.PopViewController(isAnimated); break; case NavigationMode.Forward: case NavigationMode.New: if (this.Log().IsEnabled(LogLevel.Debug)) { this.Log().Debug($"Pushing ViewController ({page.GetType().Name}) to replicate Frame's forward navigation."); } NavigationController.PushViewController(viewController, isAnimated); break; case NavigationMode.Refresh: default: // Refresh currently doesn't have an effect. break; } } } else { // We shouldn't get here because the frame events are synchronous. if (frameRequest == null) { this.Log().Error($"Can't process OnFrameNavigated because the request queue is empty."); } else { this.Log().Error($"Can't process OnFrameNavigated because the request in queue doesn't match the current request."); } } }