Xamarin.IOS go back from CNContactViewController.FromContact to Application - ios

I have an application written with Xamarin for IOS and Android where I have a list of contacts and when I click on one of them I want to open the according contact in the addressbook of the phone. I have an interface for that implement the method on IOS that way:
private void ShowContactDialog(string addressbookId, bool isEdit = false)
{
var contact = new AddressBookService().GetCnContactById(addressbookId);
var view = CNContactViewController.FromContact(contact);
view.Editing = isEdit;
DisplayView(view);
}
private static void DisplayView(CNContactViewController view)
{
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
{
vc = vc.PresentedViewController;
}
if (vc is UINavigationController navController)
{
vc = navController.ViewControllers.Last();
}
vc.PresentViewController(new UINavigationController(view), true, null);
}
That works so far as it opens the contact:
The issue now is, that there is just an edit button, but now Done, cancel or back button. Therefore I have to kill the whole application and start it again to come back.
Is there a way to add a extra button similar to the edit button to dismiss the dialog again?
EDIT: I adjusted the code as suggested in the comments by Kevin Li.
private void ShowContactDialog(string addressbookId, bool isEdit = false)
{
var contact = new AddressBookService().GetCnContactById(addressbookId);
var view = CNContactViewController.FromContact(contact);
view.Editing = isEdit;
GetCurrentViewController().NavigationController.PushViewController(view, true);
}
private static UIViewController GetCurrentViewController()
{
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
{
vc = vc.PresentedViewController;
}
if (vc is UINavigationController navController)
{
vc = navController.ViewControllers.Last();
}
return vc;
}
Now it is shown within the Tabs:
Unfortunately is the back button not displayed as well.
I tried to make a new UINavigationController, but that didn't change anything.
EDIT2:
I adjusted the way the tabs are created another time. My MainViewController looks like that:
[MvxRootPresentation(WrapInNavigationController = true)]
public class MainViewController : MvxTabBarViewController<MainViewModel>
{
private bool constructed;
private bool firstTimePresented = true;
public MainViewController()
{
constructed = true;
// need this additional call to ViewDidLoad because UIkit creates the view before the C# hierarchy has been constructed
ViewDidLoad();
}
public override async void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if (firstTimePresented)
{
// Initialize the tabs
firstTimePresented = false;
await ViewModel.GoToContactListCommand.ExecuteAsync();
await ViewModel.GoToBirthdayListCommand.ExecuteAsync();
await ViewModel.GoToProfileListCommand.ExecuteAsync();
await ViewModel.GoToSettingsCommand.ExecuteAsync();
}
}
public override void ViewDidLoad()
{
if (!constructed)
return;
base.ViewDidLoad();
Title = "Connect Update";
EdgesForExtendedLayout = UIRectEdge.None;
View.BackgroundColor = UIColor.White;
}
}
On each View who shall be a tab has to be this Attribute:
[MvxTabPresentation(TabIconName = "IconCake", TabName = "Birthdays")]
public partial class BirthdayView : MvxViewController<BirthdayViewModel>
The code for opening the contact is the same as above. Now it makes a new View without the tabs but with the back button, exactly as wished :)

The issue now is, that there is just an edit button, but now Done, cancel or back button.
It is because the value of isEdit at view.Editing = isEdit; is false. So, when you present the CNContactViewController, there's only an Edit button. When you click the Edit button, it will change to the editing mode and the Cancel and Done buttons will appear.
Or you can set the value of isEdit to true, then the Cancel and Done buttons will show when the CNContactViewController presents.
Therefore I have to kill the whole application and start it again to come back.
Is there a way to add a extra button similar to the edit button to dismiss the dialog again?
You don't have to kill and restart the app to dismiss the dialog. You can just implement the DidComplete in the delegate: CNContactViewControllerDelegate to dismiss the dialog when user finishes editing the contact and click the Done button or cancel it with clicking the Cancel button.
Here's the related code snippet:
var view = CNContactViewController.FromContact(contact);
view.Delegate = new MyCNConatactViewControllerDelegate();
public class MyCNConatactViewControllerDelegate : CNContactViewControllerDelegate
{
public override void DidComplete(CNContactViewController viewController, Contacts.CNContact contact)
{
viewController.NavigationController.DismissViewController(true, null);
}
}
Update:
To show the Back button in the CNContactViewController:
Make sure the UI hierarchy like this: Navigation->Viewcontroller(Page)->CNContactViewController. Then change the method from Present to Push to show the CNContactViewController, like this:
this.NavigationController.PushViewController(view, true);

Related

Xamarin iOS: Kill off async event handlers when a view controller is removed from the navigation stack

My RootViewController is a UINavigationController. The navigation stack starts with ViewController which then pushes CustomViewController.
public class ViewController : UIViewController
{
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
View.BackgroundColor = UIColor.Red;
NavigationController.PushViewController(new CustomViewController(), true);
}
}
public class CustomViewController : UIViewController
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
View.BackgroundColor = UIColor.DarkGray;
var button = new UIButton
{
TranslatesAutoresizingMaskIntoConstraints = false
};
View.AddSubview(button);
button.SetTitle("Click me", UIControlState.Normal);
button.WidthAnchor.ConstraintEqualTo(250).Active = true;
button.HeightAnchor.ConstraintEqualTo(50).Active = true;
button.CenterXAnchor.ConstraintEqualTo(View.CenterXAnchor).Active = true;
button.CenterYAnchor.ConstraintEqualTo(View.CenterYAnchor).Active = true;
button.AddTarget(async (sender, e) =>
{
await Task.Delay(3000);
NavigationController.PushViewController(new CustomViewController(), true);
}, UIControlEvent.TouchUpInside);
}
}
The problem I'm having is when the user clicks the button and pops its containing view controller immediately afterwards, the async event handler keeps running. After 3 seconds, it will attempt to push the same view controller, but NavigationController is set to null.
When a page is dismissed / popped off the stack, is there a way to cancel all running async methods? Note that my actual use case involves more complex interactions. I tried using a CancellationToken (ThrowIfCancellationRequested()) which helps with REST calls, but it is still possible that the user might go back to the previous page after the API call is done and before the async handler finishes.
I'm trying to find a generic solution to this because my app will eventually have dozens of async event handlers.

IOS ContactUI navigate back to app after edit

In my Xamarin IOS app I open the ContactUI with the following code:
var contact = new AddressBookService().GetCNContactById(addressbookId);
var view = CNContactViewController.FromNewContact(contact);
view.Editing = true;
// Display the view
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
{
vc = vc.PresentedViewController;
}
var navController = vc as UINavigationController;
if (navController != null)
{
vc = navController.ViewControllers.Last();
}
vc.PresentViewController(new UINavigationController(view), true, null);
I wrap it in a INavigationController because without it won't load. There is the following message printed to the output:
[CNUI ERROR] Contact view delayed appearance timed out
The Edit Dialog is displayed correctly. But after I click on Save I come to the details view:
As you can see is the second problem, that there is no back button. So the only way to come back to my app is to kill the app and start again.
Is there a way to navigate directly back to my app after I saved the contact? Or to wrap the ContactUI in my current ViewController so that the TabBar is still visible below?
Implement the CNContactViewControllerDelegate and use the DidComplete delegate of CNContactViewController. DidComplete will be fired when the "Done" button is clicked.
Set the delegate:
var view = CNContactViewController.FromNewContact(contact);
view.Delegate = new MyCNConatactViewControllerDelegate();
Implement the DidComplete, and dismiss the NavigationController which contains your CNContactViewController here:
public class MyCNConatactViewControllerDelegate : CNContactViewControllerDelegate
{
public override void DidComplete(CNContactViewController viewController, CNContact contact)
{
viewController.NavigationController.DismissViewController(true, null);
}
}

Difficulty with TabBarControllers intermixed with NavigationControllers

I have a Xamarin.iOS app where I have the following structure:
NavController (root: Login)
--> TabBarController (Home) - (Search) - (Profile)
------> NavController (root: Home)
------------->TableController
----------------->DetailController
------>NavController (root: Search)
...etc
I am currently having difficulties working with the navigation items, specifically the back button item.
I never want to go back to the login page via the back button, so in my HomeController, I hide the back button by saying
TabBarController.NavigationItem.HidesBackButton = true;
When I go to the next screen (TableController) I want to see the back button that goes back to the HomeController, however my current approach has a back button to the Login Controller
this.TabBarController.NavigationItem.HidesBackButton = false;
Thanks for any help
For the effect you want, I suggest you create an object(I use "RootController" as an example) to manage the app's Window.RootController.
I don't know if you have some experience for change window's RootController, so just follow my step:
At first create a new project, remove the storyboard and Viewcontroller.cs.(Don't forget remove the bundle for storyboard in the info.plist)
Then rewrite your AppDelegate.cs, like this:
[Register ("AppDelegate")]
public class AppDelegate : UIApplicationDelegate
{
UIWindow window;
public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
{
// create a new window instance based on the screen size
window = new UIWindow (UIScreen.MainScreen.Bounds);
window.RootViewController = RootController.Instance.LoginController;
window.MakeKeyAndVisible ();
return true;
}
public override void OnResignActivation (UIApplication application)
{
// Invoked when the application is about to move from active to inactive state.
// This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message)
// or when the user quits the application and it begins the transition to the background state.
// Games should use this method to pause the game.
}
public override void DidEnterBackground (UIApplication application)
{
// Use this method to release shared resources, save user data, invalidate timers and store the application state.
// If your application supports background exection this method is called instead of WillTerminate when the user quits.
}
public override void WillEnterForeground (UIApplication application)
{
// Called as part of the transiton from background to active state.
// Here you can undo many of the changes made on entering the background.
}
public override void OnActivated (UIApplication application)
{
// Restart any tasks that were paused (or not yet started) while the application was inactive.
// If the application was previously in the background, optionally refresh the user interface.
}
public override void WillTerminate (UIApplication application)
{
// Called when the application is about to terminate. Save data, if needed. See also DidEnterBackground.
}
}
And this is RootController.cs which include a UINavigationController and a UITabBarController(Home - Search - Profile):
public class RootController
{
private static RootController instance;
public static RootController Instance {
get {
if (instance == null)
instance = new RootController ();
return instance;
}
}
private static UINavigationController loginController;
public UINavigationController LoginController {
get {
if (loginController == null)
InitLoginController ();
return loginController;
}
}
private void InitLoginController()
{
UIViewController loginViewController = new UIViewController (){ Title = "LoginController" };
loginViewController.View.Frame = UIScreen.MainScreen.Bounds;
loginViewController.View.BackgroundColor = UIColor.Red;
loginViewController.NavigationItem.SetRightBarButtonItem (new UIBarButtonItem ("MainTab",UIBarButtonItemStyle.Done, delegate {
UIApplication.SharedApplication.KeyWindow.RootViewController = RootController.Instance.MainTabController;
}),true);
loginController = new UINavigationController (loginViewController);
}
private static UITabBarController mainTabController;
public UITabBarController MainTabController {
get {
if (mainTabController == null)
InitMainTabController ();
return mainTabController;
}
set {
mainTabController = value;
}
}
private void InitMainTabController ()
{
mainTabController = new UITabBarController ();
mainTabController.ViewControllers = new UIViewController [] {
new UINavigationController(new HomeViewController() {
TabBarItem = new UITabBarItem (UITabBarSystemItem.Favorites,0)
}),
new UINavigationController (new UIViewController ()
{
Title = "SearchController",
TabBarItem = new UITabBarItem (UITabBarSystemItem.Search,1)
}),
new UINavigationController (new UIViewController ()
{
Title = "ProfileController",
TabBarItem = new UITabBarItem (UITabBarSystemItem.More,2)
})
};
mainTabController.SelectedIndex = 0;
}
}
And this is HomeController.cs which can push an UITableViewController as you wish and also has a button to return to LoginController if you needed:
public class HomeViewController : UIViewController
{
public HomeViewController ()
{
Title = "HomeController";
this.View.Frame = UIScreen.MainScreen.Bounds;
this.View.BackgroundColor = UIColor.Green;
//Set a button to return to loginController
this.NavigationItem.SetLeftBarButtonItem (new UIBarButtonItem ("LoginC",UIBarButtonItemStyle.Done, delegate {
UIApplication.SharedApplication.KeyWindow.RootViewController = RootController.Instance.LoginController;
}),true);
//Set a button to go to tableController
UITableViewController tableViewController = new UITableViewController (){ Title = "TableViewController" };
tableViewController.View.Frame = UIScreen.MainScreen.Bounds;
tableViewController.View.BackgroundColor = UIColor.White;
tableViewController.HidesBottomBarWhenPushed = true;//To make tabbar disappear
this.NavigationItem.SetRightBarButtonItem (new UIBarButtonItem ("TableView",UIBarButtonItemStyle.Done, delegate {
this.NavigationController.PushViewController(tableViewController,true);
}),true);
}
}
You can add your own ViewController in RootController.cs instead of my sample controllers.
If you still have some problems, just leave it here, I'll check it latter.
Hope it can help you.

DialogViewController not showing navigationbar

I am instantiating a DialogViewController within a MvvmCross View like this
var myDialog = new MyDialogViewController();
var navController = new UINavigationController();
controller.NavigationBar.TintColor = UIColor.Black;
controller.PushViewController(myDialog, false);
Add(controller.View);
And this is how the MyDialogViewController looks like:
public class MyDialogViewController : DialogViewController
{
public MyDialogViewController()
: base(UITableViewStyle.Grouped, new RootElement("MyRoot"), true)
{
var root = new RootElement("MyRoot") {
new Section
{
new HtmlElement("MyWebsite", https://www.mywebsite.com")
}
};
Root.Add(root);
}
}
The dialog appears fine with the NavigationBar but if I select the Html element MyWebsite the Webview is displayed but without the NavigationBar and I am not able to navigate back.
The same thing occurs for elements that require to navigate to a new window, the navigationbar is not shown.
Any idea how to make the NavigationBar show after navigating to the WebView?
This worked for me:
var settings = new SettingsDialogViewController((SettingsViewModel)ViewModel, new RootElement("Settings"));
var window = UIApplication.SharedApplication.KeyWindow;
navigationController = window.RootViewController.ChildViewControllers[1].NavigationController;
navigationController.PushViewController(settings, true);
navigationController.NavigationBarHidden = false;

MonoTouch for IPad: How to show another UIViewController in a UIPopoverController?

As title said, I want to show another UIViewController from an existing UIViewController which is hosted in UIPopoverController. I tried the following method:
_secondViewController = new SecondViewController();
this.ModalPresentationStyle = UIModelPresentationStyle.CurrentContext;
this.ModelInPopover = true;
this.PresentModelViewController(_secondViewController, true);
However, the secondViewController is shown in the main view controller, instead of the popover controller.
In this post somebody mentions that it cannot be done and it violates the HIG. However, I have seen this in other apps (e.g. Yahoo! Email) if I'm not mistaken.
I'm also thinking about another approach: If I could create a UINavigationController within the popover context, it might work by just adding new ViewController to the NavigationController. But how?
Remember that UINavigationController derives from UIViewController.
So, you can use the controller contained within UIPopover just like any other container... in this case it's best to use UINavigationController inside UIPopover to display ViewControllers.
Usage:
var _NavController = new NavController();
Popover = new UIPopoverController(_NavController);
Popover.PopoverContentSize = new SizeF(..., ...);
Popover.PresentFromRect(...);
NavController:
public class NavController : UINavigationController
{
UIViewController _FirstViewController;
UIViewController _SecondViewController;
public NavController()
: base()
{
}
public override void LoadView()
{
base.LoadView();
_FirstViewController = new UIViewController();
// Initialize your originating View Controller here.
// Only view related init goes here, do everything else in ViewDidLoad()
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// When a button inside the first ViewController is clicked
// The Second ViewController is shown in the stack.
_FirstViewController.NavButton.TouchUpInside += delegate {
PushSecondViewController();
};
this.PushViewController(_FirstViewController, true);
}
public void PushSecondViewController()
{
_SecondViewController = new UIViewController();
this.PushViewController(_SecondViewController, true);
}
}

Resources