How to show iOS add contact screen from Xamarin Forms? - xamarin

I'm trying to show the iOS add contact screen using Xamarin Forms. From what I can see Xamarin Forms does not support this out of the box but Xamarin iOS does. Unfortunately I can't get them to work together. What I mean by "together" is that I need get access to NavigationController from Xamarin Forms Page.
Can this be done?
I have a sample solution that demonstrates the problem here: https://github.com/pawelpabich/XamarinFormsContacts. I also put the most important code below.
public void ShowContact(NavigationPage page)
{
var newPersonController = new ABNewPersonViewController();
var person = new ABPerson();
person.FirstName = "John";
person.LastName = "Doe";
newPersonController.DisplayedPerson = person;
var controller = page.CreateViewController();
//!!!!---> controller.NavigationController is null !!!!!<----
controller.NavigationController.PushViewController(newPersonController, true);
}
I updated the repo and it now contains code that works.

There is a UINavigationController when using Xamarin.Forms (when using a NavigationPage), but you have to search for it. This was the only way I could get a hold of it. Those other methods, CreateViewController and RendererFactory actually create a new ViewController which isn't what you wanted.
public void ShowContact(NavigationPage page)
{
var newPersonController = new ABNewPersonViewController();
var person = new ABPerson();
person.FirstName = "John";
person.LastName = "Doe";
newPersonController.Title = "This is a test";
newPersonController.DisplayedPerson = person;
UINavigationController nav = null;
foreach (var vc in
UIApplication.SharedApplication.Windows[0].RootViewController.ChildViewControllers)
{
if (vc is UINavigationController)
nav = (UINavigationController)vc;
}
nav.PresentModalViewController(new UINavigationController (newPersonController), true);
}
I also attempted to Create a PersonPage and PersonPageRenderer, as that would be the cleanest solution, but I couldn't get it working. This could work if you spent some time.
[assembly: ExportRenderer(typeof(PersonPage), typeof(PersonPageRenderer))]
public class PersonPageRenderer : ABNewPersonViewController, IVisualElementRenderer, IDisposable, IRegisterable

Pawel, the problem is that when you use Xamarin.Forms no NavigationController is created (as I know at least in X.F 1.3+, maybe Michael will prove me wrong). If you want to create new address boo element you can use this approach - How do you add contacts to the iPhone Address book with monotouch?

Because iOS Add Contact screen is a Native iOS API and your application logic is in a PCL you need to use a DependancyService.
1) To do this in the PCL create a Interface which provides the functionality, like
public interface ILocalAddContact
{
void DisplayContactScreen(Contact contact)
}
2) Implement the Interface in the Native Applications:
public class LocalAddContactiOS : ILocalAddContact
{
public void DisplayContactScreen(Contact contact)
{
//... do iOS Magic
}
}
3) Register the Dependancy in the Top of the Native File
[assembly: Xamarin.Forms.Dependency(typeof(LocalAddContactiOS))]
4) Obtain the Dependancy from the iOS Project from the
var addContact = DependencyService.Get<ILocalAddContact> ();
addContact.DisplayContactScreen (contact);
If you take a look at this sample application on github, it's very similar (but is used for CreateCalendar).

Ok, this is how I finally implemented. I created UINavigationController manually and use it for navigations outside Xamarin.Forms.
using System;
using System.Collections.Generic;
using System.Linq;
using Foundation;
using UIKit;
using Xamarin.Forms;
using AddressBookUI;
using AddressBook;
namespace TestContacts.iOS
{
[Register ("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
UIWindow window;
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init ();
window = new UIWindow(UIScreen.MainScreen.Bounds);
var nav =
new UINavigationController(new App ().MainPage.CreateViewController());
ContactsShared.Instance = new TouchContacts (nav);
window.RootViewController = nav;
window.MakeKeyAndVisible();
return true;
}
}
public class TouchContacts : IContactsShared {
UINavigationController nav;
public TouchContacts(UINavigationController nav){
this.nav = nav;
}
public void Show() {
var newPersonController = new ABNewPersonViewController();
newPersonController.NewPersonComplete +=
(object sender, ABNewPersonCompleteEventArgs e) =>
{
nav.PopViewController(true);
};
var person = new ABPerson();
person.FirstName = "John";
person.LastName = "Doe";
newPersonController.DisplayedPerson = person;
nav.PushViewController(newPersonController, true);
}
}
}

Related

MvvmCross migration causing a Xamarin Custom iOS View Presenter issue

While creating a CustomIosViewPresenter (of type MvxIosViewPresenter), in MVVMCross 5.x, there was a Show override that I was able to use to get the IMvxIosView so as to update the UIViewController presentation style using the PresentationValues from the ViewModel.
I had this code and it worked:
// Worked before
public override void Show(IMvxIosView view, MvvmCross.ViewModels.MvxViewModelRequest request)
{
if (request.PresentationValues != null)
{
if (request.PresentationValues.ContainsKey("NavigationMode") &&
request.PresentationValues["NavigationMode"] == "WrapInModalWithNavController")
{
var vc = view as IModalPresentation;
vc.ModalPresentationAttribute = new MvxModalPresentationAttribute
{
WrapInNavigationController = true,
ModalPresentationStyle = UIModalPresentationStyle.OverFullScreen,
ModalTransitionStyle = UIModalTransitionStyle.CoverVertical
};
}
}
base.Show(view, request);
}
But after migrating to MvvmCross 7.1, the older override doesn't work anymore and I have to use this instead, but there is no view passed into the Show override, how do I get it?
I tried this code below, but view is null and it's not able to cast it this way var view = viewType as IMvxIosView;
// Doesn't work now
public override Task<bool> Show(MvxViewModelRequest request)
{
if (request.PresentationValues != null)
{
if (request.PresentationValues.ContainsKey("NavigationMode") &&
request.PresentationValues["NavigationMode"] == "WrapInModalWithNavController")
{
var viewsContainer = Mvx.IoCProvider.Resolve<IMvxViewsContainer>();
var viewType = viewsContainer.GetViewType(request.ViewModelType);
var view = viewType as IMvxIosView;
var vc = view as IModalPresentation;
vc.ModalPresentationAttribute = new MvxModalPresentationAttribute
{
WrapInNavigationController = true,
ModalPresentationStyle = UIModalPresentationStyle.OverFullScreen,
ModalTransitionStyle = UIModalTransitionStyle.CoverVertical
};
}
}
return base.Show(request);
}
The reason I need this is because without this function when I close the special flow of view controllers that need this, its not closing all the view controllers in that flow, it closes only one of them at a time.
What you would normally do with MvvmCross if you want to navigate within a Modal ViewController is firstly add a MvxModalPresentationAttribute to the modal that will host the rest of the navigation where you set WrapInNavigationController to true.
For the children, it would just be regular child navigation, no attributes needed.
If you then want to control how the modal is popping you would create your own MvxPresentationHint and register it in your presenter using AddPresentationHintHandler.
Then you would in your ViewModel where you want to change the presentation call NavigationService.ChangePresentation(your hint).
As for the Presentation Hint, it should probably just call CloseModalViewControllers and that would probably do what you want.
TLDR: Feel for the developers that will come after you and build stuff the right way
So I dug into the MvvmCross MvxIosViewPresenter source code and was able to use this new override CreateOverridePresentationAttributeViewInstance()
I needed the request object to see the presentation values so I updated the Show function that gets called before the other override as follows:
MvxViewModelRequest _request;
public override Task<bool> Show(MvxViewModelRequest request)
{
_request = request;
return base.Show(request);
}
And I was able to get the ViewController this way, in order to selectively present it as a modal:
{
var view = base.CreateOverridePresentationAttributeViewInstance(viewType);
if (_request.PresentationValues.ContainsKey("NavigationMode") &&
_request.PresentationValues["NavigationMode"] == "WrapInModalWithNavController")
{
var vc = view as IModalPresentation;
vc.ModalPresentationAttribute = new MvxModalPresentationAttribute
{
WrapInNavigationController = true,
ModalPresentationStyle = UIModalPresentationStyle.OverFullScreen,
ModalTransitionStyle = UIModalTransitionStyle.CoverVertical
};
return vc;
}
return view;
}
And then the closing of the modal was another challenge, that I was able to figure out using the TryCloseViewControllerInsideStack and ChangePresentation overrides

What is the difference between WKNavigationDelegate and WKUIDelegate

Can I use both of them in a project?
I need to override WKUIDelegate's CreateWebView method in order to open target=_blank links:
public override WKWebView CreateWebView(WKWebView webView, WKWebViewConfiguration configuration, WKNavigationAction navigationAction, WKWindowFeatures windowFeatures)
{
var url = navigationAction.Request.Url;
if (navigationAction.TargetFrame == null)
{
webView.LoadRequest(navigationAction.Request);
}
return null;
}
When I use WKUIDelegate in a demo it works (opens target _blank). But in real project they used WKNavigationDelegate too. And applying WKUIDelegate CreateWebView doesn't work.
OnElementChange in the renderer is like this:
var config = new WKWebViewConfiguration { };
webView = new WKWebView(Frame, config);
// Set the delegate here
webView = new WKWebView(this.Frame, new WKWebViewConfiguration());
webView.ScrollView.ScrollEnabled = true;
webView.ScrollView.Bounces = true;
webView.NavigationDelegate = new DisplayLinkWebViewDelegate();
webView.UIDelegate = MyWkWebViewDelegate();
SetNativeControl(webView);
WKNavigationDelegate : It helps you implement custom behaviors that are triggered during a web view's process of accepting, loading, and completing a navigation request.
And the WKUIDelegate class provides methods for presenting native user interface elements on behalf of a webpage.
The webpage here is not the webview ,but the html which been loaded on webview.
As we can see in following image
The method in WKUIDelegate are all associated with JS.
For more details about the two protocols you can check https://developer.apple.com/documentation/webkit/wkuidelegate?language=objc
and
https://developer.apple.com/documentation/webkit/wknavigationdelegate?language=objc
if you want to do something when the webview finished loading, you can implement the method DidFinishNavigation in WKNavigationDelegate .
public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
if(!webView.IsLoading)
{
// do some thing you want
}
}

Xamarin: detect page pushed on NavigationRenderer

I order to apply some navigationBar properties (like as the background image) for different page, I think to have a condition on my custom NavigationRenderer.
My idea is to have some condition like (in my working code)
public class CustomNavigationRenderer : NavigationRenderer
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
if (pagePushed is 1)
{
NavigationBar.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
NavigationBar.ShadowImage = new UIImage();
}
else (ahother page){
var img = UIImage.FromBundle("MyImage");
NavigationBar.SetBackgroundImage(img, UIBarMetrics.Default);
}
}
}
that allows me to have at least a condition to apply a different navigation properties. Another way is to have 2 Navigationrenderer class but I think is not possible.
Any idea to how do that?
If you look at the source code for NavigationRenderer here, you will notice there are quite a few methods and callbacks you can take advantage of.
I would suggest you can do something like this:
1) Code for your custom NavigationRenderer (iOS project, you will have to do something similar on Android):
using System.Threading.Tasks;
using MyProject.iOS;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(NavigationPage), typeof(NavRenderer))]
namespace MyProject.iOS
{
public class NavRenderer : NavigationRenderer
{
protected override async Task<bool> OnPushAsync(Page page, bool animated)
{
var result = await base.OnPushAsync(page, animated);
if(result)
{
if (page is IMyPageType1)
{
NavigationBar.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
NavigationBar.ShadowImage = new UIImage();
}
else if(page is IMyPageType2)
{
var img = UIImage.FromBundle("MyImage");
NavigationBar.SetBackgroundImage(img, UIBarMetrics.Default);
}
}
return result;
}
}
}
2) Based on the code above, you need to add two interfaces. These should be located in the same project / dll where your Pages are located (all your Xamarin.Forms UI):
public interface IMyPageType1
{
}
public interface IMyPageType2
{
}
3) Now everything that's remaining is implement the interfaces on the pages where you need it. For example:
public partial class MyPage1 : ContentPage, IMyPageType1
{
//...
}
From here, possibilities are endless! You can add for example a method to IMyPageType1 that would return a color, and then inside your renderer, once you know the page being pushed is implementing IMyPageType1, you can call the method and get the color to use.

How do you switch pages in Xamarin.Forms?

How do you switch between pages in Xamarin Forms?
My main page is a ContentPage and I don't want to switch to something like a Tabbed Page.
I've been able to pseudo-do it by finding parents of the controls that should trigger the new page until I find the ContentPage and then swap out the Content with controls for a new page. But this seems really sloppy.
In the App class you can set the MainPage to a Navigation Page and set the root page to your ContentPage:
public App ()
{
// The root page of your application
MainPage = new NavigationPage( new FirstContentPage() );
}
Then in your first ContentPage call:
Navigation.PushAsync (new SecondContentPage ());
Xamarin.Forms supports multiple navigation hosts built-in:
NavigationPage, where the next page slide in,
TabbedPage, the one you don't like
CarouselPage, that allows for switching left and right to next/prev pages.
On top of this, all pages also supports PushModalAsync() which just push a new page on top of the existing one.
At the very end, if you want to make sure the user can't get back to the previous page (using a gesture or the back hardware button), you can keep the same Page displayed and replace its Content.
The suggested options of replacing the root page works as well, but you'll have to handle that differently for each platform.
If your project has been set up as a PCL forms project (and very likely as Shared Forms as well but I haven't tried that) there is a class App.cs that looks like this:
public class App
{
public static Page GetMainPage ()
{
AuditorDB.Model.Extensions.AutoTimestamp = true;
return new NavigationPage (new LoginPage ());
}
}
you can modify the GetMainPage method to return a new TabbedPaged or some other page you have defined in the project
From there on you can add commands or event handlers to execute code and do
// to show OtherPage and be able to go back
Navigation.PushAsync(new OtherPage());
// to show AnotherPage and not have a Back button
Navigation.PushModalAsync(new AnotherPage());
// to go back one step on the navigation stack
Navigation.PopAsync();
Push a new page onto the stack, then remove the current page. This results in a switch.
item.Tapped += async (sender, e) => {
await Navigation.PushAsync (new SecondPage ());
Navigation.RemovePage(this);
};
You need to be in a Navigation Page first:
MainPage = NavigationPage(new FirstPage());
Switching content isn't ideal as you have just one big page and one set of page events like OnAppearing ect.
If you do not want to go the previous page i.e. do not let the user go back to the login screen once authorization is done, then you can use;
App.Current.MainPage = new HomePage();
If you want to enable back functionality, just use
Navigation.PushModalAsync(new HomePage())
Seems like this thread is very popular and it will be sad not to mention here that there is an alternative way - ViewModel First Navigation. Most of the MVVM frameworks out there using it, however if you want to understand what it is about, continue reading.
All the official Xamarin.Forms documentation is demonstrating a simple, yet slightly not MVVM pure solution. That is because the Page(View) should know nothing about the ViewModel and vice versa. Here is a great example of this violation:
// C# version
public partial class MyPage : ContentPage
{
public MyPage()
{
InitializeComponent();
// Violation
this.BindingContext = new MyViewModel();
}
}
// XAML version
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:MyApp.ViewModel"
x:Class="MyApp.Views.MyPage">
<ContentPage.BindingContext>
<!-- Violation -->
<viewmodels:MyViewModel />
</ContentPage.BindingContext>
</ContentPage>
If you have a 2 pages application this approach might be good for you. However if you are working on a big enterprise solution you better go with a ViewModel First Navigation approach. It is slightly more complicated but much cleaner approach that allow you to navigate between ViewModels instead of navigation between Pages(Views). One of the advantages beside clear separation of concerns is that you could easily pass parameters to the next ViewModel or execute an async initialization code right after navigation. Now to details.
(I will try to simplify all the code examples as much as possible).
1. First of all we need a place where we could register all our objects and optionally define their lifetime. For this matter we can use an IOC container, you can choose one yourself. In this example I will use Autofac(it is one of the fastest available). We can keep a reference to it in the App so it will be available globally (not a good idea, but needed for simplification):
public class DependencyResolver
{
static IContainer container;
public DependencyResolver(params Module[] modules)
{
var builder = new ContainerBuilder();
if (modules != null)
foreach (var module in modules)
builder.RegisterModule(module);
container = builder.Build();
}
public T Resolve<T>() => container.Resolve<T>();
public object Resolve(Type type) => container.Resolve(type);
}
public partial class App : Application
{
public DependencyResolver DependencyResolver { get; }
// Pass here platform specific dependencies
public App(Module platformIocModule)
{
InitializeComponent();
DependencyResolver = new DependencyResolver(platformIocModule, new IocModule());
MainPage = new WelcomeView();
}
/* The rest of the code ... */
}
2.We will need an object responsible for retrieving a Page (View) for a specific ViewModel and vice versa. The second case might be useful in case of setting the root/main page of the app. For that we should agree on a simple convention that all the ViewModels should be in ViewModels directory and Pages(Views) should be in the Views directory. In other words ViewModels should live in [MyApp].ViewModels namespace and Pages(Views) in [MyApp].Views namespace. In addition to that we should agree that WelcomeView(Page) should have a WelcomeViewModel and etc. Here is a code example of a mapper:
public class TypeMapperService
{
public Type MapViewModelToView(Type viewModelType)
{
var viewName = viewModelType.FullName.Replace("Model", string.Empty);
var viewAssemblyName = GetTypeAssemblyName(viewModelType);
var viewTypeName = GenerateTypeName("{0}, {1}", viewName, viewAssemblyName);
return Type.GetType(viewTypeName);
}
public Type MapViewToViewModel(Type viewType)
{
var viewModelName = viewType.FullName.Replace(".Views.", ".ViewModels.");
var viewModelAssemblyName = GetTypeAssemblyName(viewType);
var viewTypeModelName = GenerateTypeName("{0}Model, {1}", viewModelName, viewModelAssemblyName);
return Type.GetType(viewTypeModelName);
}
string GetTypeAssemblyName(Type type) => type.GetTypeInfo().Assembly.FullName;
string GenerateTypeName(string format, string typeName, string assemblyName) =>
string.Format(CultureInfo.InvariantCulture, format, typeName, assemblyName);
}
3.For the case of setting a root page we will need sort of ViewModelLocator that will set the BindingContext automatically:
public static class ViewModelLocator
{
public static readonly BindableProperty AutoWireViewModelProperty =
BindableProperty.CreateAttached("AutoWireViewModel", typeof(bool), typeof(ViewModelLocator), default(bool), propertyChanged: OnAutoWireViewModelChanged);
public static bool GetAutoWireViewModel(BindableObject bindable) =>
(bool)bindable.GetValue(AutoWireViewModelProperty);
public static void SetAutoWireViewModel(BindableObject bindable, bool value) =>
bindable.SetValue(AutoWireViewModelProperty, value);
static ITypeMapperService mapper = (Application.Current as App).DependencyResolver.Resolve<ITypeMapperService>();
static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as Element;
var viewType = view.GetType();
var viewModelType = mapper.MapViewToViewModel(viewType);
var viewModel = (Application.Current as App).DependencyResolver.Resolve(viewModelType);
view.BindingContext = viewModel;
}
}
// Usage example
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:MyApp.ViewModel"
viewmodels:ViewModelLocator.AutoWireViewModel="true"
x:Class="MyApp.Views.MyPage">
</ContentPage>
4.Finally we will need a NavigationService that will support ViewModel First Navigation approach:
public class NavigationService
{
TypeMapperService mapperService { get; }
public NavigationService(TypeMapperService mapperService)
{
this.mapperService = mapperService;
}
protected Page CreatePage(Type viewModelType)
{
Type pageType = mapperService.MapViewModelToView(viewModelType);
if (pageType == null)
{
throw new Exception($"Cannot locate page type for {viewModelType}");
}
return Activator.CreateInstance(pageType) as Page;
}
protected Page GetCurrentPage()
{
var mainPage = Application.Current.MainPage;
if (mainPage is MasterDetailPage)
{
return ((MasterDetailPage)mainPage).Detail;
}
// TabbedPage : MultiPage<Page>
// CarouselPage : MultiPage<ContentPage>
if (mainPage is TabbedPage || mainPage is CarouselPage)
{
return ((MultiPage<Page>)mainPage).CurrentPage;
}
return mainPage;
}
public Task PushAsync(Page page, bool animated = true)
{
var navigationPage = Application.Current.MainPage as NavigationPage;
return navigationPage.PushAsync(page, animated);
}
public Task PopAsync(bool animated = true)
{
var mainPage = Application.Current.MainPage as NavigationPage;
return mainPage.Navigation.PopAsync(animated);
}
public Task PushModalAsync<TViewModel>(object parameter = null, bool animated = true) where TViewModel : BaseViewModel =>
InternalPushModalAsync(typeof(TViewModel), animated, parameter);
public Task PopModalAsync(bool animated = true)
{
var mainPage = GetCurrentPage();
if (mainPage != null)
return mainPage.Navigation.PopModalAsync(animated);
throw new Exception("Current page is null.");
}
async Task InternalPushModalAsync(Type viewModelType, bool animated, object parameter)
{
var page = CreatePage(viewModelType);
var currentNavigationPage = GetCurrentPage();
if (currentNavigationPage != null)
{
await currentNavigationPage.Navigation.PushModalAsync(page, animated);
}
else
{
throw new Exception("Current page is null.");
}
await (page.BindingContext as BaseViewModel).InitializeAsync(parameter);
}
}
As you may see there is a BaseViewModel - abstract base class for all the ViewModels where you can define methods like InitializeAsync that will get executed right after the navigation. And here is an example of navigation:
public class WelcomeViewModel : BaseViewModel
{
public ICommand NewGameCmd { get; }
public ICommand TopScoreCmd { get; }
public ICommand AboutCmd { get; }
public WelcomeViewModel(INavigationService navigation) : base(navigation)
{
NewGameCmd = new Command(async () => await Navigation.PushModalAsync<GameViewModel>());
TopScoreCmd = new Command(async () => await navigation.PushModalAsync<TopScoreViewModel>());
AboutCmd = new Command(async () => await navigation.PushModalAsync<AboutViewModel>());
}
}
As you understand this approach is more complicated, harder to debug and might be confusing. However there are many advantages plus you actually don't have to implement it yourself since most of the MVVM frameworks support it out of the box. The code example that is demonstrated here is available on github. There are plenty of good articles about ViewModel First Navigation approach and there is a free Enterprise Application Patterns using Xamarin.Forms eBook which is explaining this and many other interesting topics in detail.
By using the PushAsync() method you can push and PopModalAsync() you can pop pages to and from the navigation stack. In my code example below I have a Navigation page (Root Page) and from this page I push a content page that is a login page once I am complete with my login page I pop back to the root page
~~~ Navigation can be thought of as a last-in, first-out stack of Page objects.To move from one page to another an application will push a new page onto this stack. To return back to the previous page the application will pop the current page from the stack. This navigation in Xamarin.Forms is handled by the INavigation interface
Xamarin.Forms has a NavigationPage class that implements this interface and will manage the stack of Pages. The NavigationPage class will also add a navigation bar to the top of the screen that displays a title and will also have a platform appropriate Back button that will return to the previous page. The following code shows how to wrap a NavigationPage around the first page in an application:
Reference to content listed above and a link you should review for more information on Xamarin Forms, see the Navigation section:
http://developer.xamarin.com/guides/cross-platform/xamarin-forms/introduction-to-xamarin-forms/
~~~
public class MainActivity : AndroidActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
Xamarin.Forms.Forms.Init(this, bundle);
// Set our view from the "main" layout resource
SetPage(BuildView());
}
static Page BuildView()
{
var mainNav = new NavigationPage(new RootPage());
return mainNav;
}
}
public class RootPage : ContentPage
{
async void ShowLoginDialog()
{
var page = new LoginPage();
await Navigation.PushModalAsync(page);
}
}
//Removed code for simplicity only the pop is displayed
private async void AuthenticationResult(bool isValid)
{
await navigation.PopModalAsync();
}
In App.Xaml.Cs:
MainPage = new NavigationPage( new YourPage());
When you wish to navigate from YourPage to the next page you do:
await Navigation.PushAsync(new YourSecondPage());
You can read more about Xamarin Forms navigation here: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/navigation/hierarchical
Microsoft has quite good docs on this.
There is also the newer concept of the Shell. It allows for a new way of structuring your application and simplifies navigation in some cases.
Intro: https://devblogs.microsoft.com/xamarin/shell-xamarin-forms-4-0-getting-started/
Video on basics of Shell: https://www.youtube.com/watch?v=0y1bUAcOjZY&t=3112s
Docs: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/shell/
Call:
((App)App.Current).ChangeScreen(new Map());
Create this method inside App.xaml.cs:
public void ChangeScreen(Page page)
{
MainPage = page;
}
In Xamarin we have page called NavigationPage. It holds stack of ContentPages.
NavigationPage has method like PushAsync() and PopAsync(). PushAsync add a page at the top of the stack, at that time that page will become the currently active page. PopAsync() method remove the page from the top of the stack.
In App.Xaml.Cs set like.
MainPage = new NavigationPage( new YourPage());
From YourPage you await Navigation.PushAsync(new newPage()); this method will add newPage at the top of the stack. At this time newPage will be currently active page.
One page to another page navigation in Xamarin.forms using Navigation property Below sample code
void addClicked(object sender, EventArgs e)
{
//var createEmp = (Employee)BindingContext;
Employee emp = new Employee();
emp.Address = AddressEntry.Text;
App.Database.SaveItem(emp);
this.Navigation.PushAsync(new EmployeeDetails());
this.Navigation.PushModalAsync(new EmployeeDetails());
}
To navigate one page to another page with in view cell Below code Xamrian.forms
private async void BtnEdit_Clicked1(object sender, EventArgs e)
{
App.Database.GetItem(empid);
await App.Current.MainPage.Navigation.PushModalAsync(new EmployeeRegistration(empid));
}
Example like below
public class OptionsViewCell : ViewCell
{
int empid;
Button btnEdit;
public OptionsViewCell()
{
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (this.BindingContext == null)
return;
dynamic obj = BindingContext;
empid = Convert.ToInt32(obj.Eid);
var lblname = new Label
{
BackgroundColor = Color.Lime,
Text = obj.Ename,
};
var lblAddress = new Label
{
BackgroundColor = Color.Yellow,
Text = obj.Address,
};
var lblphonenumber = new Label
{
BackgroundColor = Color.Pink,
Text = obj.phonenumber,
};
var lblemail = new Label
{
BackgroundColor = Color.Purple,
Text = obj.email,
};
var lbleid = new Label
{
BackgroundColor = Color.Silver,
Text = (empid).ToString(),
};
//var lbleid = new Label
//{
// BackgroundColor = Color.Silver,
// // HorizontalOptions = LayoutOptions.CenterAndExpand
//};
//lbleid.SetBinding(Label.TextProperty, "Eid");
Button btnDelete = new Button
{
BackgroundColor = Color.Gray,
Text = "Delete",
//WidthRequest = 15,
//HeightRequest = 20,
TextColor = Color.Red,
HorizontalOptions = LayoutOptions.EndAndExpand,
};
btnDelete.Clicked += BtnDelete_Clicked;
//btnDelete.PropertyChanged += BtnDelete_PropertyChanged;
btnEdit = new Button
{
BackgroundColor = Color.Gray,
Text = "Edit",
TextColor = Color.Green,
};
// lbleid.SetBinding(Label.TextProperty, "Eid");
btnEdit.Clicked += BtnEdit_Clicked1; ;
//btnEdit.Clicked += async (s, e) =>{
// await App.Current.MainPage.Navigation.PushModalAsync(new EmployeeRegistration());
//};
View = new StackLayout()
{
Orientation = StackOrientation.Horizontal,
BackgroundColor = Color.White,
Children = { lbleid, lblname, lblAddress, lblemail, lblphonenumber, btnDelete, btnEdit },
};
}
private async void BtnEdit_Clicked1(object sender, EventArgs e)
{
App.Database.GetItem(empid);
await App.Current.MainPage.Navigation.PushModalAsync(new EmployeeRegistration(empid));
}
private void BtnDelete_Clicked(object sender, EventArgs e)
{
// var eid = Convert.ToInt32(empid);
// var item = (Xamarin.Forms.Button)sender;
int eid = empid;
App.Database.DeleteItem(empid);
}
}
After PushAsync use PopAsync (with this) to remove current page.
await Navigation.PushAsync(new YourSecondPage());
this.Navigation.PopAsync(this);
XAML page add this
<ContentPage.ToolbarItems>
<ToolbarItem Text="Next" Order="Primary"
Activated="Handle_Activated"/>
</ContentPage.ToolbarItems>
on the CS page
async void Handle_Activated(object sender, System.EventArgs e)
{
await App.Navigator.PushAsync(new PAGE());
}

How can a scroll a specific row or section to the top of a Xamarin.Forms.ListView

I have a Xamarin.Forms.ListView that contains events that are grouped by date. There are events that occur in future and events that occur in the past.
Users would like to have their screen load with a future event closest to the current date in view so that they do not need to manually scroll down to view it.
What options do I have with a Xamarin.Forms.ListView to accomplish this for iOS and Android users?
I have made some progress. I am able to accomplish my goal in iOS by creating a CustomListView and an iOS render to support it.
In Xamarin.Forms you create a CustomListView then after you have loaded the list you an call ScrollToRow(item,section) to manually scroll to the row you need.
In iOS the renderer maps the method to UITableView message ScrollToRow(...);
For Android I still need to create the renderer but I do know that I need to map to the calls getListView().setSelection(...); or getListView().smoothScrollToPosition(...);
I am sure there is a more elegant way to do this but for now it is getting the job done
Source For: Common.CustomListView
using System;
using Xamarin.Forms;
namespace Common {
public class CustomListView : ListView {
public Action<int, int, bool> ScrollToRowDelegate { get; set; }
public void ScrollToRow(int itemIndex, int sectionIndex = 0, bool animated = false) {
if (ScrollToRowDelegate != null) {
ScrollToRowDelegate (itemIndex, sectionIndex, animated);
}
}
}
}
iOS Renderer Source:YourApplication.iOS.Renderers.CustomListViewRenderer
using System;
using Xamarin.Forms.Platform.iOS;
using Xamarin.Forms;
using Common;
using MonoTouch.UIKit;
using MonoTouch.Foundation;
using YourApplication.iOS.Renderers;
[assembly: ExportRenderer (typeof(CustomListView), typeof(CustomListViewRenderer))]
namespace YourApplication.iOS.Renderers
{
public class CustomListViewRenderer : ListViewRenderer
{
protected override void OnModelSet (VisualElement view) {
base.OnModelSet (view);
var listView = view as CustomListView;
listView.ScrollToRowDelegate = (itemIndex, sectionIndex, animated) => {
ScrollToRow(itemIndex, sectionIndex, animated);
};
}
private void ScrollToRow(int itemIndex, int sectionIndex, bool animated) {
var tableView = this.Control as UITableView;
var indexPath = NSIndexPath.FromItemSection (itemIndex, sectionIndex);
tableView.ScrollToRow (indexPath, UITableViewScrollPosition.Top, animated);
}
}
}

Resources