How can I set ios:Page.UseSafeArea="true" in background .cs files - xamarin

Here's my XAML Code:
<t:HeadingViewBase
xmlns ="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:t="clr-namespace:M.Templates"
x:Name="ContentPage"
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
ios:Page.UseSafeArea="true"
ios:Page.PrefersHomeIndicatorAutoHidden="true"
Does anyone know how I can set the UseSafeArea to be true in the background CS ?
Here is what I tried:
public HeadingView()
{
InitializeComponent();
BindingContext = this;
Xamarin.Forms.PlatformConfiguration.iOSSpecific.Page.UsingSafeArea = true;
Xamarin.Forms.PlatformConfiguration.iOSSpecific.Page.PrefersHomeIndicatorAutoHidden = true;
}
However it gives me error messages saying:
/Users/r/Projects/M1/M/Templates/Pages/HeadingView.xaml.cs(13,13):
Error CS1656: Cannot assign to 'UsingSafeArea' because it is a 'method
group' (CS1656)
/Users/r/Projects/M1/M/Templates/Pages/HeadingView.xaml.cs(13,13):
Error CS1656: Cannot assign to 'PrefersHomeIndicatorAutoHidden'
because it is a 'method group' (CS1656)

Platform specifics are defined as extensions via a (static) BindableProperty so it is usable via XAML, along a static GetXXXX and SetXXXX extension for code usage.
So, UseSafeArea is defined as a Page class extension, thus
yourPageInstance.On<iOS>.SetUseSafeArea(true);
var safeAreaValue = yourPageInstance.On<iOS>.GetUseSafeArea();
re: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/platform/platform-specifics/#consuming-the-platform-specific

Related

Xamarin - PushAsync fails when using ContentPage.BindingContext

I have two pages, the main page, which displays informations, and a login page, which should be shown, if I'm not logged in. The main page get it's informations via the ContentPage.BindingContext attribute. I'm using following method to show the login screen: Application.Current.MainPage.Navigation.PushModalAsync(new LoginPage());
When I run the app with the ContentPage.BindingContext, the code fails with System.NullReferenceException: 'Object reference not set to an instance of an object.'. If I remove the BindingContext, start the app, and add the BindingContext with Xamarin Hot Reload, the login screen will be displayed.
Here is an excerpt from the code:
MainPage.xaml
<?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:local="clr-namespace:MyCalendar"
xmlns:iOsSpecific="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
x:Class="MyCalendar.MainPage"
iOsSpecific:Page.UseSafeArea="True">
<ContentPage.BindingContext>
<local:MyViewModel/>
</ContentPage.BindingContext>
</ContentPage>
MyViewModel.cs
public class MyViewModel : INotifyPropertyChanged
{
private readonly MyData data;
public MyViewModel()
{
data = new MyData();
}
protected void RaisePropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
MyData.cs
public class MyData
{
public MyData()
{
// Fetching data .....
case Error:
Application.Current.MainPage.Navigation.PushModalAsync(new LoginPage(), true);
break;
case OK:
// Execute code
break;
default:
Console.WriteLine("SOME ERROR");
break;
}
}
Is there any way to show the login page?
The problem was that the BindingContext, when set in the .xaml, still executed before the MainPage was (fully) initialized. Accordingly, an NPE always came.
I was able to fix it simply by adding a new line MainPage.BindingContext = new MyViewModel(); in the App.xaml.cs and removing the BindingContext from the .xaml. This ensured that the BindingContext is only executed when the MainPage is completely loaded.

How to set AutomationId on TabbedPage bar items in Xamarin Forms app for UI test

I'm writing tests in Xamarin UI Test for a tab-based Xamarin Forms app. I'd like to set the automation Ids on each tab item so that my UI Test can click a specific tab, without referring to the tab's Text label, which is localized.
I imagine you need to use a custom renderer and set ContentDescription (Android) and AccessibilityIdentifier (iOS), and I've been trying to do that, with mixed results. What is the correct way to do this? If I'm on the right track with custom renderer, which renderer method(s) should I override in IOS/Android to achieve this?
UPDATE:
iOS:
Answer was provided by #apineda. See his solution below the question.
Android: Seems to required a custom renderer. It's a little yucky but it works. We have to recursively search the view hierarchy for the tab bar items and set "ContentDescription" for each. Since we are using a bottom-navigation bar, we search backwards for better performance. For topside navigation bar, you'll need to search for "TabLayout" instead of "BottomNavigationItemView".
[assembly: ExportRenderer(typeof(MainPage), typeof(CustomTabbedPageRenderer))]
namespace Company.Project.Droid.CustomRenderers
{
public class CustomTabbedPageRenderer : TabbedRenderer
{
private bool tabsSet = false;
public CustomTabbedPageRenderer(Context context)
: base(context)
{
}
protected override void DispatchDraw(Canvas canvas)
{
if (!tabsSet)
{
SetTabsContentDescription(this);
}
base.DispatchDraw(canvas);
}
private void SetTabsContentDescription(Android.Views.ViewGroup viewGroup)
{
if (tabsSet)
{
return;
}
// loop through the view hierarchy backwards. this will work faster since the tab bar
// is at the bottom of the page
for (int i = viewGroup.ChildCount -1; i >= 0; i--)
{
var menuItem = viewGroup.GetChildAt(i) as BottomNavigationItemView;
if (menuItem != null)
{
menuItem.ContentDescription = "TabBarItem" + i.ToString();
// mark the tabs as set, so we don't do this loop again
tabsSet = true;
}
else
{
var viewGroupChild = viewGroup.GetChildAt(i) as Android.Views.ViewGroup;
if (viewGroupChild != null && viewGroupChild.ChildCount > 0)
{
SetTabsContentDescription(viewGroupChild);
}
}
}
}
}
}
You don't need CustomRenderer for this. You just need to set the AutomationId to the children Pages of the TabPage and this is assigned to the bar Item.
Let's say you have this TabPage as below
<?xml version="1.0" encoding="UTF-8"?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyGreatNamespace"
x:Class="MyGreatNamespace.MyTabPage">
<TabbedPage.Children>
<local:MainPage AutomationId="MainTab" Title="Main Page" />
<local:PageOne AutomationId="TabOne" Title="Page One" />
<local:PageTwo AutomationId="TabTwo" Title="Page Two" />
</TabbedPage.Children>
</TabbedPage>
With this configuration you will be able to do:
app.Tap("TabTwo");
And you won't need to use the Text property.
Hope this helps.-
UPDATE:
Just confirmed the above does not work with Android (noticed your original question is for Android) but only with iOS. For some reason the behavior is different.
You can still use the Localized version of the Text to "Tap it" as explained below.
A trick you can use when dealing with Localized Text is that you set the right Culture then use the same resource set in the XAML as part of the Test.
i.e
app.Tap(AppResources.MyMainTabText);

Xamarin Forms Display image from Embedded resource using XAML

I'm trying to display and embedded image in a shared project resource (as explained here on microsoft documentation).
I created the ImageResourceExtension, the project compiles, but I get the following error when I start the project :
Could not load type 'Xamarin.Forms.Xaml.IReferenceProvider' from
assembly 'Xamarin.Forms.Core, Version=2.0.0.0
Here's my code :
MainPage.xaml
<?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:local="clr-namespace:App1"
x:Class="App1.MainPage">
<StackLayout>
<Image x:Name="imgTest" Source="{local:ImageResource img.jpg}" />
</StackLayout>
</ContentPage>
EmbeddedImage.cs
namespace App1
{
[ContentProperty(nameof(Source))]
public class ImageResourceExtension : IMarkupExtension
{
public string Source { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (Source == null)
return null;
// Do your translation lookup here, using whatever method you require
var imageSource = ImageSource.FromResource(Source, typeof(ImageResourceExtension).GetTypeInfo().Assembly);
return imageSource;
}
}
}
You can try this, it works for me
xyz.xaml.cs
imgName.Source = ImageSource.FromResource("ProjectName.Images.backicon.png");
xyz.xaml
<Image x:Name="imgName" />
Hope this code helps you!!
Not knowing if the answer is still valid for you, but I usually have an Assets folder outside my solution which contains Android specific folders (drawables), iOS specific folders (Resource with #2x and #3x) and a Common folder for shared images.
In my solution I add these files as a link in the folders they should be associated with. On Android in the Resources/Drawable and iOS in the Resource. In this case Common files can be shared across both platforms without physically copying them.
As a bonus, updates to these Assets can be overwritten in the folder outside my solution and will be used within my solution without any additional action to be taken.
Replace:
<Image x:Name="imgTest" Source="{local:ImageResource img.jpg}" />
With:
<Image x:Name="imgTest" Source="{local:ImageResource 'Project.Folder.img.jpg'}" />
And Replace:
var imageSource = ImageSource.FromResource(Source, typeof(...));
With:
var imageSource = ImageSource.FromResource(Source);
If you add the Image to your Android drawable folder, or to the iOS Resource folder, you don't need any extension. You can simply do:
<Image x:Name="imgTest" Source="img.jpg" />

Xamarin Forms - Adding constructor to MasterDetailPage.Master

I have a Xamarin.Forms app with a master-detail page and it works well.
But I've recently needed to add a parameter to the constructor of the master page (AttendPageMaster), but now I need to pass this constructor.
How do I add a parameter to the xaml?
<MasterDetailPage.Master>
<pages:AttendPageMaster x:Name="MasterPage" />
</MasterDetailPage.Master>
The code behind page with constructor:
public AttendPageMaster(AttendanceViewModel viewModel)
{
}
Let me know if you need any more info.
You do not have to pass ViewModel to Page via constructor, you can set the Page's BindingContext:
<MasterDetailPage.Master>
<pages:AttendPageMaster x:Name="MasterPage">
<pages:AttendPageMaster.BindingContext>
<myViewModels:AttendanceViewModel />
</pages:AttendPageMaster.BindingContext>
</pages:AttendPageMaster>
</MasterDetailPage.Master>
This solution will work if your ViewModel does not expect any parameters in constructor. Otherwise you may consider using ViewModelLocator and DI to inject the constructor parameters.
Please note that myViewModels should be defined in the header of your XAML page as xmlns:myViewModels.
P.S.: Previously you mentioned that you got an exception while trying to use code behind approach. You could easily solve it by setting the Title property of the AttendPageMaster. Example:
new AttendPageMaster(new AttendanceViewModel()){ Title = " " };
I managed to do this from the code behind by creating the menu page in the constructor of the masterdetail and assigning it to the "Master" property:
AttendMasterPageMaster MasterPage;
public AttendMasterPage(AttendanceViewModel viewModel)
{
MasterPage = new AttendMasterPageMaster(viewModel);
Detail = new NavigationPage((Page)Activator.CreateInstance(typeof(StartPage), viewModel));
Master = MasterPage;

Xamarin Forms Force view to bind values

I have a view. I have a bindable property there.
public partial class OrderCard : ContentView
{
public static readonly BindableProperty OrderProperty = BindableProperty.Create(nameof(Order), typeof(Order), typeof(OrderCard), null);
public Order Order
{
get { return (Order)GetValue(OrderProperty); }
set { SetValue(OrderProperty, value); }
}
public OrderCard()
{
InitializeComponent();
}
}
In the xaml of this view I'm binding to Order property like this:
Text="{Binding Order.Name, Source={x:Reference Root}}"
Root is a name in the xaml of a view OrderCard
When I use this view in the page everything works ok.
But I want to measure it's size even before adding it to the page.
var orderCard = new OrderCard { Order = order};
SizeRequest sizeRequest = orderCard.Measure(OrdersContainer.Width/5, OrdersContainer.Height);
But it gives me wrong numbers because bindings isn't applied.
How to force to apply bindings when view isn't attached to the page?
Bindings do not require being attached to a Page or anything else to be applied.
You might think they're not applied if your method for figuring out is to set a breakpoint on get_Order because that is never used, the Xaml loader uses GetValue directly. The usual way of figuring out if a Binding is correctly applied, is to add a PassthroughConverter (don't look for it, you have to write it yourself) to the Binding and put the breakpoint in the Convert method.
That being said, you can't Measure anything unless it's added to a page that is rendered on screen. If you try to Measure before that, you indeed get dummy values.
I was able to solve this problem by not doing a property Order but passing an order as BindingContext. Then I can measure the size of a view without attaching it to a page like this:
var orderCard = new OrderCard { BindingContext = order};
SizeRequest sizeRequest = orderCard.Measure(widthToTryToFitInTheView,heightToTryToFitInTheView);

Resources