Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access ViewController in DependencyService to present MFMailComposeViewController

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?

like image 590
FrTerstappen Avatar asked Jun 10 '14 08:06

FrTerstappen


5 Answers

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");
    })
}
like image 196
Stephane Delcroix Avatar answered Nov 14 '22 01:11

Stephane Delcroix


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

like image 44
Klaasel Avatar answered Nov 14 '22 00:11

Klaasel


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.

like image 5
TChadwick Avatar answered Nov 14 '22 01:11

TChadwick


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

This only works in above all solutions

like image 2
sreekanth_bantu Avatar answered Nov 14 '22 01:11

sreekanth_bantu


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);
like image 1
Bartosz Węgielewski Avatar answered Nov 14 '22 01:11

Bartosz Węgielewski