How to load a screen within the iOS app using Xamarin.Forms Shell feature for navigation?

Viewed 55

With cross-platform Xamarin.Forms, I am working with an mobile application by using Shell feature for navigation. I integrated a third party payment gateway using its SDK in my project on both platforms(Android and iOS). I am using dependency service to initiate the payment process in both platforms. For iOS app, payment screen didn't load inside the app. Instead it opens in new window outside the app. All other screens are working fine. For Android, there is no problem. After searching a lot of articles, I found out that in iOS, root view controller is not available when using Xamarin.Forms. I didn't use any storyboard except default LaunchScreen.storyboard.

Xamarin.Forms version: 5.0.0.2478, Mac OS version: 15, Visual Studio for Mac: 2019

My AppDelegate is

[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
 public override bool FinishedLaunching(UIApplication app, NSDictionary options)
 {
   global::Xamarin.Forms.Forms.SetFlags("CollectionView_Experimental");
   global::Xamarin.Forms.Forms.Init();    
   LoadApplication(new App());    
   return base.FinishedLaunching(app, options);
 }
}

Here my AppDelegate is inherited by Xamarin.Forms.Platform.iOS.FormsApplicationDelegate instead of UIApplicationDelegate. Due to this, the value for UIApplication.SharedApplication.KeyWindow.RootViewController is NULL.

I tried to initialize an empty view controller during the app initialization, for that AppDelegate will be as below

UIWindow window;
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
 {
   global::Xamarin.Forms.Forms.Init();    
   LoadApplication(new App());    
   window = new UIWindow(UIScreen.MainScreen.Bounds);
   window.RootViewController = new UIViewController();
   window.MakeKeyAndVisible();    
   return base.FinishedLaunching(app, options);
 }

This won't works and still the payment screen loads outside the app. I also added a view controller in the dependency injection class in iOS project,

public void PaymentRequest(PaymentData paymentData)
{
  Payment payment = new Payment();                  // From payment SDK
  payment.RefNo = paymentData.ReferenceNumber;
  …
  …
  PayObject payObj = new PayObject();               
  …
  …    
  UIViewController responseVC = new UIViewController();
  UIView responseView = new UIView();
  responseView.Frame = UIScreen.MainScreen.Bounds;
  responseView = payObj.Checkout(payment);
  responseVC.View = responseView;    
  UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(responseVC, true, null);
}

With this too, payment screen loads outside the app. PresentViewController loads the view modally as per the documentation. We have to push a view controller into UINavigationController in iOS under native method. But here as we know that Xamarin.Forms dynamically handle those things. So, I don't know how to access the iOS root view controller in order to load the payment screen inside the Xamarin.Forms iOS app.

2 Answers

Well I guess the easy way to do it would be to get the CurrentViewController from the currently displayed window.

public UIViewController GetCurrentUIViewController()
    {
        UIViewController viewController;
        var window = UIApplication.SharedApplication.KeyWindow;
        if (window == null)
        {
            return null;
        }

        if (window.RootViewController.PresentedViewController == null)
        {
            window = UIApplication.SharedApplication.Windows
                     .First(i => i.RootViewController != null &&
                                 i.RootViewController.GetType().FullName
                                 .Contains(typeof(Xamarin.Forms.Platform.iOS.Platform).FullName));
        }

        viewController = window.RootViewController;

        while (viewController.PresentedViewController != null)
        {
            viewController = viewController.PresentedViewController;
        }

        return viewController;
    }

Then use it where you need it :

var currentViewController= GetCurrentUIViewController();
currentViewController?.PresentViewController(responseVC, true, null);

Good luck

The KeyWindow method is deprecated in iOS13 and cannot get the root view. You can replace it with the following code:

UIApplication.SharedApplication.Delegate.GetWindow().RootViewController
Related