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;
Related
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);
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);
}
}
How do I add a web view for each of these tabs in my UIViewController?
public class TabController: UITabBarController
{
UIViewController tab1, tab2, tab3, tab4, tab5, tab6, tab7;
public TabController()
{
tab1 = new UIViewController();
tab1.Title = "Test";
tab1.View.BackgroundColor = UIColor.Orange;
tab2 = new UIViewController();
tab2.Title = "Test2";
tab2.View.BackgroundColor = UIColor.Orange;
tab3 = new UIViewController();
tab3.Title = "Test3";
tab3.View.BackgroundColor = UIColor.Red;
tab4 = new UIViewController();
tab4.Title = "Test4";
tab4.View.BackgroundColor = UIColor.Red;
tab5 = new UIViewController();
tab5.Title = "Test5";
tab5.View.BackgroundColor = UIColor.Red;
tab6 = new UIViewController();
tab6.Title = "Test6";
tab6.View.BackgroundColor = UIColor.Red;
tab7 = new UIViewController();
tab7.Title = "Test7";
tab7.View.BackgroundColor = UIColor.Red;
var UIViewController = new UIViewController[] {
tab1, tab2, tab3, tab4, tab5, tab6, tab7
};
tab1.TabBarItem = new UITabBarItem(UITabBarSystemItem.Favorites, 0);
ViewControllers = UIViewController;
}
}
So for each tab, instead of "tab1.View.BackgroundColor = UIColor.Orange;", it
should be:
Webview + Title + Link so that each tab has a different page?
The easiest way would be to do this using the storyboard and assigning the viewcontroller classes to your tabs. However if you want to do it programatically I suppose you would need to do the following:
tab7 = new UIViewController();
tab7.Title = "Test7";
tab7.View.BackgroundColor = UIColor.Red;
//Create a webview
UIWebView webView = new UIWebView(View.Bounds); //Takes size as a constructor parameter
//Set it's navigation location
webView.Navigate(new URI("www.whatever.co.uk"));
//Add the webview to the subviews of your UIViewController
tab7.View.AddSubview(webView);
You'd do this for each tab.
On a side note it's worth mentioning that apples guidelines for application submissions to their store does tend to frown upon applications which are just web site representations, so worth bearing that in mind. The application must be able to do more than apples web browser could.
Is it possible to call a method when a new UIViewController is selected in a UITabBarController ? Or is it possible to wait loading the whole UIViewController until the view is selecte?
I have a UITabBarController that looks like:
public sealed class VisualizationViewController : UITabBarController
{
private UIViewController tabVand, tabVarme, tabEl;
private UIToolbar toolbar;
public VisualizationViewController()
{
toolbar = new UIToolbar(new RectangleF(0, 0, (float)View.Frame.Width, 60));
toolbar.AutoresizingMask = UIViewAutoresizing.FlexibleWidth;
View.AddSubview(toolbar);
tabVand = new WaterVisualizationViewController();
tabVand.Title = "Vand";
tabVand.TabBarItem = new UITabBarItem("Vand", UIImage.FromFile("Images/Water.png"), 0); // Tag == initial order
tabVand.TabBarItem.SetFinishedImages( UIImage.FromFile("Images/Water.png"), UIImage.FromFile("Images/Water.png"));
tabVand.View.BackgroundColor = UIColor.LightGray;
tabVarme = new TemperatureViewController();
tabVarme.Title = "Varme";
tabVarme.TabBarItem = new UITabBarItem("Varme", UIImage.FromFile("Images/Heat.png"), 1); // Tag == initial order
tabVarme.TabBarItem.SetFinishedImages(UIImage.FromFile("Images/Heat.png"), UIImage.FromFile("Images/Heat.png"));
tabVarme.View.BackgroundColor = UIColor.LightGray;
tabEl = new ElVisualizationViewController();
tabEl.Title = "EL";
tabEl.TabBarItem = new UITabBarItem("EL", UIImage.FromFile("Images/Power.png"), 2); // Tag == initial order
tabEl.TabBarItem.SetFinishedImages(UIImage.FromFile("Images/Power.png"), UIImage.FromFile("Images/Power.png"));
tabEl.View.BackgroundColor = UIColor.LightGray;
var tabs = new[]
{
tabVand, tabVarme, tabEl
};
ViewControllers = tabs;
SelectedViewController = tabVand;
}
And right now, it loads all the Views, which is going to be an expensive call, when I move from dummy data to the real data each view should load. So I want to only make a data call when the view is selected in the UITabBarController.
So what's happening is that your ViewDidLoad() method is firing (assuming you're calling your webservice there) for each tab view controller in your constructor. Each View Did Load is firing because you're calling View.Background, which will load the view into memory. You don't want to do that here, but in the ViewDidLoad() method of each individual tab View Controller:
public sealed class VisualizationViewController : UITabBarController
{
private UIViewController tabVand, tabVarme, tabEl;
private UIToolbar toolbar;
public VisualizationViewController()
{
base.ViewDidLoad();
toolbar = new UIToolbar(new RectangleF(0, 0, (float)View.Frame.Width, 60));
toolbar.AutoresizingMask = UIViewAutoresizing.FlexibleWidth;
View.AddSubview(toolbar);
tabVand = new WaterVisualizationViewController();
tabVand.Title = "Vand";
tabVand.TabBarItem = new UITabBarItem("Vand", UIImage.FromFile("Images/Water.png"), 0); // Tag == initial order
tabVand.TabBarItem.SetFinishedImages( UIImage.FromFile("Images/Water.png"), UIImage.FromFile("Images/Water.png"));
//Don't do this, it will make its ViewDidLoad fire
//and you don't want that.
//tabVand.View.BackgroundColor = UIColor.LightGray;
tabVarme = new TemperatureViewController();
tabVarme.Title = "Varme";
tabVarme.TabBarItem = new UITabBarItem("Varme", UIImage.FromFile("Images/Heat.png"), 1); // Tag == initial order
tabVarme.TabBarItem.SetFinishedImages(UIImage.FromFile("Images/Heat.png"), UIImage.FromFile("Images/Heat.png"));
//tabVarme.View.BackgroundColor = UIColor.LightGray;
tabEl = new ElVisualizationViewController();
tabEl.Title = "EL";
tabEl.TabBarItem = new UITabBarItem("EL", UIImage.FromFile("Images/Power.png"), 2); // Tag == initial order
tabEl.TabBarItem.SetFinishedImages(UIImage.FromFile("Images/Power.png"), UIImage.FromFile("Images/Power.png"));
//tabEl.View.BackgroundColor = UIColor.LightGray;
var tabs = new[]
{
tabVand, tabVarme, tabEl
};
ViewControllers = tabs;
SelectedViewController = tabVand;
}
Then in each individual tab (UIViewController) implementation, call your web service in it's ViewDidLoad() method, and not in the constructor. Example:
public class WaterVisualizationViewController : UIViewController
{
public WaterVisualizationViewController()
{
//Don't call your web service here
}
public override ViewDidLoad()
{
//Set your background color here
//Call your web service
//Populate your UI with data
}
}
Now if your web service calls are expensive, you might see a little lag when tapping the tabs. For that you should consider using UIActivityIndicatorView to display a "Loading Data" type of indicator so that the user has feedback your app is doing something and not frozen. Alternatively, you can use BTProgressHUD from the Xamarin Component Store, which is really easy to use
I'm new to Xamarin IOS and I have a project which needs this flyout navigation.
var navigation = new FlyoutNavigationController {
// Create the navigation menu
NavigationRoot = new RootElement ("Navigation") {
new Section ("Menu") {
new StringElement ("Recipe"),
new StringElement ("Recipe Basket"),
new StringElement ("Meal Planner"),
new StringElement ("Grocery List"),
new StringElement ("Social"),
new StringElement ("Videos"),
new StringElement ("News Feed"),
new StringElement ("Partners"),
}
},
// Supply view controllers corresponding to menu items:
ViewControllers = new [] {
new UIViewController { View = new UILabel { Text = "Animals (drag right)" } },
new UIViewController { View = new UILabel { Text = "Vegetables (drag right)" } },
new UIViewController { View = new UILabel { Text = "Minerals (drag right)" } },
},
};
This code just presents the UILabel. How can I do this to send it to a specific view controller?
Please help.
Thanks in advance.
Do what vinaykumar said, but you also want to open the FlyoutNavigationController from those UIViewControllers, so you have to pass that initial one you created to those controllers.
Otherwise if you just open your view controller, you won't be able to get the menu again.
I'm opening navigation controllers like so:
MainViewController in ViewDidLoad:
UIStoryboard board = UIStoryboard.FromName("MainStoryboard_iPhone", null);
UINavigationController nav = (UINavigationController)board.InstantiateViewController("nav");
SearchViewController searchVC = (SearchViewController)nav.ViewControllers[0];
// Passing flyoutnavigation here
searchVC.navigation = navigation;
SettingsViewController settingsVC = (SettingsViewController)board.InstantiateViewController("settings");
UINavigationController navSettings = new UINavigationController(settingsVC);
// Passing flyoutnavigation here
settingsVC.navigation = navigation;
navigation.ViewControllers = new[]
{
nav,
navSettings
};
In the other view controllers, (Settings and Search in my example), I have a public variable to store the FlyoutNavigationController (set in MainViewController) then add a button to open the FlyoutNavigationController that was passed:
// Set in MainViewController
public FlyoutNavigationController navigation;
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Add button to allow opening the FlyoutNavigationController
NavigationItem.LeftBarButtonItem = new UIBarButtonItem(UIBarButtonSystemItem.Action, delegate
{
navigation.ToggleMenu();
});
}
you have to add the FlyoutNavigationController's View to your View
// Show the navigation view
navigation.ToggleMenu ();
View.AddSubview (navigation.View);
This code just presents the UILabel.
because you are only providing UILable as a view to your new UIViewController.
View = new UILabel { Text = "Animals (drag right)" }
How can I do this to send it to a specific view controller?
Provide your UIViewController instead of new UIViewController
for instance change the following code
ViewControllers = new [] {
new UIViewController { View = new UILabel { Text = "Animals (drag right)" } },
new UIViewController { View = new UILabel { Text = "Vegetables (drag right)" } },
new UIViewController { View = new UILabel { Text = "Minerals (drag right)" } },
},
to
ViewControllers = new [] {
new <your_UIViewController>(),
new <your_UIViewController>(),
new <your_UIViewController>(),
},
Hope this helps you, let me know if you faced any issue while implementing the same.