Access ViewController in DependencyService to present MFMailComposeViewController

12,128

Solution 1

You can present a MFMailComposeViewController by doing a window.RootController.PresentViewController (mail controller, true, null);. Depending on your app architecture, the RootViewController might not be an usable ViewController in the hierarchy. In that case you get a

Warning: Attempt to present <MFMailComposeViewController: 0x16302c30> on <Xamarin_Forms_Platform_iOS_PlatformRenderer: 0x14fd1530> whose view is not in the window hierarchy!

In that case, you have to dig for the concrete ViewController, in my case it is:

var rootController = ((AppDelegate)(UIApplication.SharedApplication.Delegate)).Window.RootViewController.ChildViewControllers[0].ChildViewControllers[1].ChildViewControllers[0];

which is a bit wicked, but works (An issue for this have been filed for future fix).

The full solution then looks like:

in your AppDelegate.cs, add this:

public UIWindow Window {
    get { return window; }
}

in your PCL project, declare the interface: ISendMailService.cs

public interface ISendMailService
{
    void ComposeMail (string[] recipients, string subject, string messagebody = null, Action<bool> completed = null);
}

in your iOS project, implement and register the interface: SendMailService.cs

[assembly: DependencyAttribute(typeof(SendMailService))]

public class SendMailService : ISendMailService
{
    public void ComposeMail (string[] recipients, string subject, string messagebody = null, Action<bool> completed = null)
    {
        var controller = new MFMailComposeViewController ();
        controller.SetToRecipients (recipients);
        controller.SetSubject (subject);
        if (!string.IsNullOrEmpty (messagebody))
            controller.SetMessageBody (messagebody, false);
        controller.Finished += (object sender, MFComposeResultEventArgs e) => {
            if (completed != null)
                completed (e.Result == MFMailComposeResult.Sent);
            e.Controller.DismissViewController (true, null);
        };

        //Adapt this to your app structure
        var rootController = ((AppDelegate)(UIApplication.SharedApplication.Delegate)).Window.RootViewController.ChildViewControllers[0].ChildViewControllers[1].ChildViewControllers[0];
        var navcontroller = rootController as UINavigationController;
        if (navcontroller != null)
            rootController = navcontroller.VisibleViewController;
        rootController.PresentViewController (controller, true, null);
    }
}

And you can now consume it from your Xamarin.Forms PCL project:

new Button {
    Font = Font.SystemFontOfSize (NamedSize.Medium),
    Text = "Contact us",
    TextColor = Color.White,
    BackgroundColor = ColorsAndStyles.LightBlue,
    BorderRadius = 0,
    Command = new Command (()=>{
        var mailservice = DependencyService.Get<ISendMailService> ();
        if (mailservice == null)
            return;
        mailservice.ComposeMail (new [] {"[email protected]"}, "Test", "Hello, World");
    })
}

Solution 2

Use: UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(controller, true, null);

Solution 3

I would like to add an additional answer based off of the KeyWindow not always being the main window. (this occurs when you are presenting your controller after the user has interacted with an action sheet or alert dialog)

public static UIViewController GetCurrentUIController()
    {
        UIViewController viewController;
        var window = UIApplication.SharedApplication.KeyWindow;
        if (window == null)
        {
            throw new InvalidOperationException("There's no current active window");
        }

        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;
    }

This will guarantee that you get the Xamarin Forms platform renderer window, then find the foremost presented ViewController and return it for use presenting whatever UI or view controller you need to present.

Solution 4

UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(controller, true, null);

This only works in above all solutions

Solution 5

Just for a reference. It took me some time to figure it out how to launch it from modal window.

Here comes the solution:

var rootController = ((AppDelegate)(UIApplication.SharedApplication.Delegate)).Window.RootViewController.PresentedViewController;
var navcontroller = rootController as UINavigationController;
if (navcontroller != null)
    rootController = navcontroller.VisibleViewController;
rootController.PresentViewController (controller, true, null);
Share:
12,128
FrTerstappen
Author by

FrTerstappen

Updated on July 29, 2022

Comments

  • FrTerstappen
    FrTerstappen almost 2 years

    How can i access the ViewController in my DependencyService to present a MFMailComposeViewController? I tried using Application.Context but this seems to be only working on Android. Any advice?

  • FrTerstappen
    FrTerstappen about 10 years
    can you give an advice how to adapt to app structure?
  • Stephane Delcroix
    Stephane Delcroix about 10 years
    @FrTerstappen: put a breakpoint, and find the "right" child with the help of the Debugger
  • FrTerstappen
    FrTerstappen about 10 years
    i can just go with the root view controller. Is this okay? or do i have to find the concrete contoller?
  • subha
    subha over 9 years
    @Stephane Delcroix: I used above code to open ViewController from Forms. Its working, but how to add navigation bar to the ViewController?I want to add navbar with back button and clicking on back should navigate to forms page. How to do it?
  • Torre Lasley
    Torre Lasley almost 9 years
    One line of code and it worked beautifully! Thank you very much
  • TChadwick
    TChadwick about 7 years
    This breaks in iOS 10 on an iPad if an Actionsheet or alert dialog is being used
  • Max Vargas
    Max Vargas about 7 years
    Thank you thank you thank you! I had spent 3 hours before reading your comment.