Access ViewController in DependencyService to present MFMailComposeViewController
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);
FrTerstappen
Updated on July 29, 2022Comments
-
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 about 10 yearscan you give an advice how to adapt to app structure?
-
Stephane Delcroix about 10 years@FrTerstappen: put a breakpoint, and find the "right" child with the help of the Debugger
-
FrTerstappen about 10 yearsi can just go with the root view controller. Is this okay? or do i have to find the concrete contoller?
-
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 almost 9 yearsOne line of code and it worked beautifully! Thank you very much
-
TChadwick about 7 yearsThis breaks in iOS 10 on an iPad if an Actionsheet or alert dialog is being used
-
Max Vargas about 7 yearsThank you thank you thank you! I had spent 3 hours before reading your comment.