Xamarin iOS webview underlap behind navigation bar - xamarin

I am using xamarin custom webview to load my page in app. But facing issue that title of webpage hides behind navigation bar . Or sometimes bottom of page not shown. I have tried adding scrollbar to my layout but still facing issue. Same works perfectly on android. Is it due to custom webview? I just want my webview to start below navigation bar and load completely according to device size.
my custom webview code :
public class CustomWebView : WebView
{
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(CustomWebView),
defaultValue: default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
}
Xaml Page :
<StackLayout Orientation="Vertical" HorizontalOptions="StartAndExpand" VerticalOptions="StartAndExpand">
<StackLayout>
<Label x:Name="type" Text="Loading..." FontSize="Medium"/>
</StackLayout>
<StackLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<ScrollView Orientation="Vertical" FlowDirection="MatchParent" HorizontalOptions="StartAndExpand" VerticalOptions="StartAndExpand" Visual="Material" VerticalScrollBarVisibility="Always">
<OnPlatform x:TypeArguments="View">
<On Platform="Android">
<WebView x:Name="dashboard_android" HeightRequest="1000" WidthRequest="1000" />
</On>
<On Platform="iOS">
<local:CustomWebView x:Name="dashboard_ios" VerticalOptions="StartAndExpand" HorizontalOptions="FillAndExpand" WidthRequest="1000" HeightRequest="1000"/>
</On>
</OnPlatform>
</ScrollView>
</StackLayout>
</StackLayout>
code behind :
dashboard_android.Source = url;
dashboard_ios.Uri = url;
Following are solutions i have tried but no success
Solution 1 :
I have tried adding two properties, but no use
this.EdgesForExtendedLayout = UIRectEdge.None;
this.ExtendedLayoutIncludesOpaqueBars = false;
Solution 2 :
Tried enabling this unsafe area property , still no success
ios:Page.UseSafeArea="true"
Solution 3 :
Tried setting webview height on content size dynamically , but no success
public override async void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
// base.DidFinishNavigation(webView, navigation);
var wv = _webViewRenderer.Element as CustomWebView;
if (wv != null)
{
await System.Threading.Tasks.Task.Delay(100); // wait here till content is rendered
wv.HeightRequest = (double)webView.Frame.Size.Height; // ScrollView.ContentSize.Height;
}
}
Updated Xaml Code :
<StackLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<local:CustomWebView x:Name="dashboard" HeightRequest="1000" WidthRequest="1000" />
</StackLayout>
Updated Code behind :
public partial class DashboardView : ContentPage
{
string url;
public DashboardView()
{
InitializeComponent();
url= ""; //adding url to load here
dashboard.Uri = url;
}
}
Custom WebView Renderer
[assembly: ExportRenderer(typeof(CustomWebView), typeof(MyCustomWebViewRenderer))]
namespace Report.iOS
{
public class MyCustomWebViewRenderer : ViewRenderer<CustomWebView, WKWebView>
{
WKWebView webView;
protected override void OnElementChanged(ElementChangedEventArgs<CustomWebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
webView = new WKWebView(Frame, new WKWebViewConfiguration());
webView.NavigationDelegate = new WebViewDelegate();
SetNativeControl(webView);
}
if (e.NewElement != null)
{
Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.Uri)));
}
}
}
public class WebViewDelegate : WKNavigationDelegate, INSUrlConnectionDataDelegate
{
string uname = null;
string pass = null;
public override async void DidReceiveAuthenticationChallenge(WKWebView webView, NSUrlAuthenticationChallenge challenge, Action<NSUrlSessionAuthChallengeDisposition, NSUrlCredential> completionHandler)
{
try
{
uname = Xamarin.Forms.Application.Current.Properties.ContainsKey("Username") ? Convert.ToString(Xamarin.Forms.Application.Current.Properties["Username"]) : null;
pass = await SecureStorage.GetAsync("Password");
}
catch (Exception ex)
{
}
completionHandler(NSUrlSessionAuthChallengeDisposition.UseCredential, new NSUrlCredential(uname, pass, NSUrlCredentialPersistence.ForSession));
return;
}
}
}
Screenshot of webview screen :
Here i am loading this webpage(https://learn.microsoft.com/en-us/xamarin/essentials/device-display?tabs=android). As you can see half of footer is hidden and i am not able to scroll it.
Screenshot of app

The reason for it quite simple actually you have added the WebView inside a scrollView which is, in turn, causing the issue webview has its own scroll so all you have to do is something like:
<StackLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<local:CustomWebView x:Name="dashboard" />
</StackLayout>
Also, you do not need the on the platform you can directly use the below and the custom renderer you have created.
The Height/Width request & layout options are not needed Webview by default will capture the whole viewport, You could actually even remove the StackLayouts, But that's on you.
Also, you might wanna read more about the webview
Good luck
Feel free to get back if you have queries

You can use latest WkWebViewRenderer:
public class MyCustomWebViewRenderer : WkWebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
//this.LoadUrl("https://learn.microsoft.com/en-us/xamarin/essentials/device-display?tabs=android");
this.NavigationDelegate = new WebViewDelegate();
}
}
In your code behind, you can directly set the source or set your binding:
dashboard.Source = "https://learn.microsoft.com/en-us/xamarin/essentials/device-display?tabs=android";
Also, start from xamarin.forms 4.5+, xamarin use WKWebview as the default control in iOS and that means you no longer need a custom renderer if you use xamarin.forms 4.5+. Refer:
UIWebView Deprecation and App Store Rejection (ITMS-90809)

I was facing that issue just beacuse i was using custom renderer.
My solution code is as follows :
Xaml Code :
<ContentPage.Content>
<StackLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<WebView x:Name="dashboard" HeightRequest="1000" WidthRequest="1000"/>
</StackLayout>
</ContentPage.Content>
Code Behind :
public partial class DashboardView : ContentPage
{
public DashboardView()
{
InitializeComponent();
dashboard.Source = "url";
}
}
Authentication Renderer iOS :
[assembly: ExportRenderer(typeof(WebView), typeof(Report.iOS.WebViewRenderer))]
namespace Report.iOS
{
class WebViewRenderer : WkWebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
this.NavigationDelegate = new WebViewDelegate();
}
}
public class WebViewDelegate : WKNavigationDelegate, INSUrlConnectionDataDelegate
{
string uname = null;
string pass = null;
public override async void DidReceiveAuthenticationChallenge(WKWebView webView, NSUrlAuthenticationChallenge challenge, Action<NSUrlSessionAuthChallengeDisposition, NSUrlCredential> completionHandler)
{
try
{
uname = Xamarin.Forms.Application.Current.Properties.ContainsKey("Username") ? Convert.ToString(Xamarin.Forms.Application.Current.Properties["Username"]) : null;
pass = await SecureStorage.GetAsync("Password");
}
catch (Exception ex)
{
}
completionHandler(NSUrlSessionAuthChallengeDisposition.UseCredential, new NSUrlCredential(uname, pass, NSUrlCredentialPersistence.ForSession));
return;
}
}
}
Authentication Renderer Android :
[assembly: ExportRenderer(typeof(WebView), typeof(AuthWebViewRenderer))]
namespace Report.Droid
{
public class AuthWebViewRenderer : Xamarin.Forms.Platform.Android.WebViewRenderer
{
AuthWebViewClient _authWebClient = null;
public AuthWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (_authWebClient == null)
{
_authWebClient = new AuthWebViewClient();
}
Control.SetWebViewClient(_authWebClient);
}
}
public class AuthWebViewClient : WebViewClient
{
public AuthWebViewClient()
{
}
public override async void OnReceivedHttpAuthRequest(global::Android.Webkit.WebView view, HttpAuthHandler handler, string host, string realm)
{
string uname = null;
string pass = null;
try
{
uname = Application.Current.Properties.ContainsKey("Username") ? Convert.ToString(Application.Current.Properties["Username"]) : null;
pass = await SecureStorage.GetAsync("Password");
}
catch (Exception ex)
{
Log.Error("Apprise :", "Error Occurred while getting login credentials " + ex);
}
handler.Proceed(uname, pass);
}
}
}

Related

Xamarin IOS Custom Renderer overriden Draw method not called

I am trying to load a customized slider control in a listview (with accordeon behaviour). When the View loads all the listview elements are collapsed so the slider control visibility is false. I observed that the overriden Draw method within the ios renderer is not called while the control is not visible so I end up having the native control within my listview.
I have reproduced the issue in a separate project:
I have the IOS custom renderer:
public class CustomGradientSliderRenderer : SliderRenderer
{
public CGColor StartColor { get; set; }
public CGColor CenterColor { get; set; }
public CGColor EndColor { get; set; }
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
{
if (Control == null)
{
var customSlider = e.NewElement as CustomGradientSlider;
StartColor = customSlider.StartColor.ToCGColor();
CenterColor = customSlider.CenterColor.ToCGColor();
EndColor = customSlider.EndColor.ToCGColor();
var slider = new SlideriOS
{
Continuous = true,
Height = (nfloat)customSlider.HeightRequest
};
SetNativeControl(slider);
}
base.OnElementChanged(e);
}
public override void Draw(CGRect rect)
{
base.Draw(rect);
if (Control != null)
{
Control.SetMinTrackImage(CreateGradientImage(rect.Size), UIControlState.Normal);
}
}
void OnControlValueChanged(object sender, EventArgs eventArgs)
{
((IElementController)Element).SetValueFromRenderer(Slider.ValueProperty, Control.Value);
}
public UIImage CreateGradientImage(CGSize rect)
{
var gradientLayer = new CAGradientLayer()
{
StartPoint = new CGPoint(0, 0.5),
EndPoint = new CGPoint(1, 0.5),
Colors = new CGColor[] { StartColor, CenterColor, EndColor },
Frame = new CGRect(0, 0, rect.Width, rect.Height),
CornerRadius = 5.0f
};
UIGraphics.BeginImageContext(gradientLayer.Frame.Size);
gradientLayer.RenderInContext(UIGraphics.GetCurrentContext());
var image = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return image.CreateResizableImage(UIEdgeInsets.Zero);
}
}
public class SlideriOS : UISlider
{
public nfloat Height { get; set; }
public override CGRect TrackRectForBounds(CGRect forBounds)
{
var rect = base.TrackRectForBounds(forBounds);
return new CGRect(rect.X, rect.Y, rect.Width, Height);
}
}
The View with codebehind:
Main.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="GradientSlider.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:GradientSlider">
<ContentPage.Content>
<Grid>
<StackLayout x:Name="SliderContainer">
<local:CustomGradientSlider
x:Name="mySlider"
CenterColor="#feeb2f"
CornerRadius="16"
EndColor="#ba0f00"
HeightRequest="20"
HorizontalOptions="FillAndExpand"
Maximum="10"
Minimum="0"
StartColor="#6bab29"
VerticalOptions="CenterAndExpand"
MaximumTrackColor="Transparent"
ThumbColor="green"
/>
<Label x:Name="lblText" Text="txt"
VerticalOptions="Center" HorizontalOptions="Center"/>
</StackLayout>
<Button Text="Magic" Clicked="Button_Tapped" WidthRequest="100" HeightRequest="50" VerticalOptions="Center" HorizontalOptions="Center"/>
</Grid>
</ContentPage.Content>
</ContentPage>
Main.xaml.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace GradientSlider
{
public partial class MainPage : ContentPage, INotifyPropertyChanged
{
public MainPage()
{
InitializeComponent();
SliderContainer.IsVisible = false;
}
void Button_Tapped(object sender,ClickedEventArgs a)
{
SliderContainer.IsVisible = !SliderContainer.IsVisible;
}
}
}
So in the scenario above you can see that when I load the main.xaml the control is invisible (SliderContainer.IsVisible = false;) in this case I get a native slider control and not my custom one. If I change in the constructor SliderContainer.IsVisible = true; then I get my custom control.
After an investigation I realised that if the control is not visible when the view loads the public override void Draw(CGRect rect) is not called. I could not find any solution to trigger the Draw method while the control is invisible.
Anybody has an idea how to load a custom renderer correctly while the control is not visible ?
Thank you!
Assuming the renderer is overriding OnElementPropertyChanged:
protected override void OnElementChanged(ElementChangedEventArgs<MyFormsSlider> e)
{
if (e.NewElement != null)
{
if (Control == null)
{
// Instantiate the native control and assign it to the Control property with
// the SetNativeControl method
SetNativeControl(new MyNativeControl(...
...
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
//assuming MyFormsSlider derives from View / VisualElement; the latter has IsVisibleProperty
if (e.PropertyName == MyFormsSlider.IsVisibleProperty.PropertyName)
{
//Control is the control set with SetNativeControl
Control. ...
}
...
}

Custom entry null xamarin

I created a custom entry in my Login page, but its getting null
I just created hte CustomEntry class and the CustomEntryRenderer, and put in the xaml file
My Login page .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:custom="clr-namespace:HCTaNaMao.Customs"
x:Class="HCTaNaMao.Views.Login">
<ContentPage.Content>
<StackLayout VerticalOptions="FillAndExpand" Padding="0,100,0,0">
<Image Source="HCbackground.png" VerticalOptions="Center" HeightRequest="200" />
<Label Text="Usuario" HorizontalTextAlignment="Center"/>
<custom:CustomEntry
x:Name=" usernameEntry"
CornerRadius="18"
IsCurvedCornersEnabled="True"
BorderColor="LightBlue"
HorizontalTextAlignment="Start"
FontSize="17"
HeightRequest="40"
Placeholder="Usuário"
PlaceholderColor="LightGray"
TextColor="Black"
FontAttributes="Bold"
WidthRequest="100"/>
<Label Text="Senha" HorizontalTextAlignment="Center"/>
<custom:CustomEntry
x:Name=" passwordEntry"
CornerRadius="18"
IsCurvedCornersEnabled="True"
BorderColor="LightBlue"
HorizontalTextAlignment="Start"
FontSize="17"
HeightRequest="40"
Placeholder="Senha"
PlaceholderColor="LightGray"
TextColor="Black"
FontAttributes="Bold"
WidthRequest="100"
IsPassword="True"/>
<Button Text="Entrar" TextColor="White" Clicked="LoginUser" WidthRequest="110"
HorizontalOptions="Center" BackgroundColor="SteelBlue" BorderRadius="20"/>
<Label x:Name="messageLabel" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
My Login page .xaml.cs
namespace HCTaNaMao.Views
{
public partial class Login : ContentPage
{
public static int seq_cliente;
public Login ()
{
InitializeComponent ();
usernameEntry.ReturnCommand = new Command(() => passwordEntry.Focus());
}
async void LoginUser(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(usernameEntry.Text) || string.IsNullOrEmpty(passwordEntry.Text))
{
if (string.IsNullOrEmpty(usernameEntry.Text))
await DisplayAlert("Usuario", "Digite o Usuario", "OK");
else
await DisplayAlert("Senha", "Digite a Senha", "OK");
return;
}
HCTMWebService service = new HCTMWebService();
seq_cliente = service.Login(usernameEntry.Text.ToUpper());
if (seq_cliente > 0)
await Navigation.PopModalAsync();
else
await DisplayAlert("Erro Login", "Usuario ou Senha errado", "OK");
}
protected override bool OnBackButtonPressed()
{
#if __ANDROID__
Android.OS.Process.KillProcess(Android.OS.Process.MyPid());
#endif
return base.OnBackButtonPressed();
}
}
}
My custom entry
namespace HCTaNaMao.Customs
{
public class CustomEntry : Entry
{
public static readonly BindableProperty BorderColorProperty =
BindableProperty.Create(
nameof(BorderColor),
typeof(Color),
typeof(CustomEntry),
Color.Gray);
// Gets or sets BorderColor value
public Color BorderColor
{
get { return (Color)GetValue(BorderColorProperty); }
set { SetValue(BorderColorProperty, value); }
}
public static readonly BindableProperty BorderWidthProperty =
BindableProperty.Create(
nameof(BorderWidth),
typeof(int),
typeof(CustomEntry),
Device.OnPlatform<int>(1, 2, 2));
// Gets or sets BorderWidth value
public int BorderWidth
{
get { return (int)GetValue(BorderWidthProperty); }
set { SetValue(BorderWidthProperty, value); }
}
public static readonly BindableProperty CornerRadiusProperty =
BindableProperty.Create(
nameof(CornerRadius),
typeof(double),
typeof(CustomEntry),
Device.OnPlatform<double>(6, 7, 7));
// Gets or sets CornerRadius value
public double CornerRadius
{
get { return (double)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
public static readonly BindableProperty IsCurvedCornersEnabledProperty =
BindableProperty.Create(
nameof(IsCurvedCornersEnabled),
typeof(bool),
typeof(CustomEntry),
true);
// Gets or sets IsCurvedCornersEnabled value
public bool IsCurvedCornersEnabled
{
get { return (bool)GetValue(IsCurvedCornersEnabledProperty); }
set { SetValue(IsCurvedCornersEnabledProperty, value); }
}
}
}
My renderer
[assembly: ExportRenderer(typeof(CustomEntry), typeof(CustomEntryRenderer))]
namespace HCTaNaMao.Droid
{
public class CustomEntryRenderer : EntryRenderer
{
public CustomEntryRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
var view = (CustomEntry)Element;
if (view.IsCurvedCornersEnabled)
{
// creating gradient drawable for the curved background
var _gradientBackground = new GradientDrawable();
_gradientBackground.SetShape(ShapeType.Rectangle);
_gradientBackground.SetColor(view.BackgroundColor.ToAndroid());
// Thickness of the stroke line
_gradientBackground.SetStroke(view.BorderWidth, view.BorderColor.ToAndroid());
// Radius for the curves
_gradientBackground.SetCornerRadius(
DpToPixels(this.Context,
Convert.ToSingle(view.CornerRadius)));
// set the background of the label
Control.SetBackground(_gradientBackground);
}
// Set padding for the internal text from border
Control.SetPadding(
(int)DpToPixels(this.Context, Convert.ToSingle(12)),
Control.PaddingTop,
(int)DpToPixels(this.Context, Convert.ToSingle(12)),
Control.PaddingBottom);
}
}
public static float DpToPixels(Context context, float valueInDp)
{
DisplayMetrics metrics = context.Resources.DisplayMetrics;
return TypedValue.ApplyDimension(ComplexUnitType.Dip, valueInDp, metrics);
}
}
}
In my Login.xaml.cs, the line
usernameEntry.ReturnCommand = new Command(() => passwordEntry.Focus());
is getting error because the usernameEntry is null
Do I have to instance it?
In your XAML, you have x:Name=" usernameEntry" with a space. You must remove the space.
You need to instantiate your custom entry before adding a command. The correct way to use a custom entry is basically as the following example:
Add StackLayouts or another content layout
<StackLayout VerticalOptions="FillAndExpand" Padding="0,100,0,0">
<Image Source="HCbackground.png" VerticalOptions="Center" HeightRequest="200" />
<Label Text="Usuario" HorizontalTextAlignment="Center"/>
<StackLayout x:Name="stlUserName">
<!-- usernameEntry add here in code behind -->
</StackLayout>
<StackLayout x:Name="stlpasswordEntry">
<!-- passwordEntry add here in code behind -->
</StackLayout>
<Button Text="Entrar" TextColor="White" Clicked="LoginUser" WidthRequest="110"
HorizontalOptions="Center" BackgroundColor="SteelBlue" BorderRadius="20"/>
<Label x:Name="messageLabel" />
</StackLayout>
In code behind instantiate your custom entry
public Login ()
{
InitializeComponent ();
CustomEntryRenderer usernameEntry = new CustomEntryRenderer();
usernameEntry.CornerRadius="18";
usernameEntry.IsCurvedCornersEnabled="True";
usernameEntry.BorderColor="LightBlue";
usernameEntry.HorizontalTextAlignment="Start";
usernameEntry.FontSize="17";
usernameEntry.HeightRequest="40";
usernameEntry.Placeholder="Usuário";
usernameEntry.PlaceholderColor="LightGray";
usernameEntry.TextColor="Black";
usernameEntry.FontAttributes="Bold";
usernameEntry.WidthRequest="100";
usernameEntry.ReturnCommand = new Command(() => passwordEntry.Focus());
// Add entry in stacklayout
stlUserName.Children.Add(usernameEntry);
// do the same for password entry
}
Note:
Some properties of your entry, as CornerRadius, need to be added correctly, the above code just demonstrates that you need to instantiate your entry, add values to your properties, and add it to a stack layout.

Get start position of a listview scroll to end in xamarin forms wpf

I have worked on getting listview scroll position scroll to end in xamarin forms WPF application. I have tried below solution, it works in ios and android but unfortunately, it doesn't work in wpf application. Please suggest any idea to get scroll position of a listview end in xamarinforms WPF application.
Sample code you can find in below link
https://stackoverflow.com/questions/40373761/how-to-set-listview-to-start-showing-the-last-item-instead-in-xamarin-forms
If you're working with Xamarin Forms, you can create a control that extend from ListView and add methods for scrolling to top or bottom.
namespace YourAppName.Controls
{
public class CustomListView : ListView
{
public CustomListView() : this(ListViewCachingStrategy.RecycleElement)
{
}
public CustomListView(ListViewCachingStrategy cachingStrategy)
: base(cachingStrategy)
{
}
public void ScrollToFirst()
{
Device.BeginInvokeOnMainThread(() =>
{
try
{
if (ItemsSource != null && ItemsSource.Cast<object>().Count() > 0)
{
var firstItem = ItemsSource.Cast<object>().FirstOrDefault();
if (firstItem != null)
{
ScrollTo(firstItem, ScrollToPosition.Start, false);
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
});
}
public void ScrollToLast()
{
try
{
if (ItemsSource != null && ItemsSource.Cast<object>().Count() > 0)
{
var lastItem = ItemsSource.Cast<object>().LastOrDefault();
if (lastItem != null)
{
ScrollTo(lastItem, ScrollToPosition.End, false);
}
}
}
catch(Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
}
}
}
And on your xaml:
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:YourAppName.Controls"
x:Class="YourAppName.Views.CustomListViewPage">
<controls:CustomListView
x:Name="customListView"
ItemsSource="{Binding Items}"
SeparatorVisibility="None"
SelectionMode="None"
HasUnevenRows="true">
<controls:CustomListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label
FontSize="Medium"
Text="{Binding TestText}" />
</ViewCell>
</DataTemplate>
</controls:CustomListView.ItemTemplate>
</controls:CustomListView>
</ContentPage>
And on the code behind you can do something like this:
namespace YourAppName.Views
public partial class CustomListViewPage : ContentPage
{
public CustomListViewPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
this.customListView.ScrollToLast();
}
}

Xamarin Forms ListView text not displayed

I'm new to Xamarin Forms, I'm following the official tutorial for learning Xamarin forms. While learning about navigation using Phoneword project of the following link
https://developer.xamarin.com/guides/xamarin-forms/getting-started/hello-xamarin-forms-multiscreen/quickstart/
The listview text is not appearing. Please help me!
CallHistoryPage.xaml: Here the listview is there.
<?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;assembly=App1"
x:Class="App1.CallHistoryPage"
Title="Call History">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="20, 40, 20, 20"/>
<On Platform="Android" Value="20"/>
</OnPlatform>
</ContentPage.Padding>
<StackLayout>
<ListView ItemsSource="{x:Static local:App.PhoneNumbers}" />
</StackLayout>
</ContentPage>
MainPage.xaml.cs: SourceItem values are updated in this class.
namespace App1
{
public partial class MainPage : ContentPage
{
string translatedNumber;
public MainPage()
{
InitializeComponent();
}
void OnTranslate(object sender, EventArgs e)
{
translatedNumber = PhonewordTranslator.ToNumber(phoneNumberText.Text);
if (!string.IsNullOrWhiteSpace(translatedNumber))
{
callButton.IsEnabled = true;
callButton.Text = "Call " + translatedNumber;
}
else
{
callButton.IsEnabled = false;
callButton.Text = "Call";
}
}
async void OnCall(object sender, EventArgs e)
{
if (await this.DisplayAlert(
"Dial a Number",
"Would you like to call " + translatedNumber + "?",
"Yes",
"No"))
{
var dialer = DependencyService.Get<IDialer>();
if (dialer != null)
{
App.PhoneNumbers.Add(translatedNumber);
callHistoryButton.IsEnabled = true;
dialer.Dial(translatedNumber);
}
}
}
async void OnCallHistory(object sender, EventArgs e)
{
await Navigation.PushAsync(new CallHistoryPage());
}
}
}
App.xaml.cs: Sourceitem for listview is in this class
namespace App1
{
public partial class App : Application
{
public static IList<string> PhoneNumbers { get; set; }
public App()
{
InitializeComponent();
PhoneNumbers = new List<string>();
MainPage = new NavigationPage(new MainPage());
}
protected override void OnStart()
{
// Handle when your app starts
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
}
For more details please follow the link added above. Same tutorial is followed.
You forgot to 'tell' ListView what to display.
<ListView ItemsSource="{x:Static local:App.PhoneNumbers}" />
creates a ListView with empty cells, hence they are not displaying anything. You'll have to set the ListView.ItemTemplate in order to display anything
<ListView ItemsSource="{x:Static local:App.PhoneNumbers}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding .}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The BindingContext within the DataTemplate will be the respective item from App.PhoneNumbers. Since the items are bare strings we bind to ., which refers to the bound element itself.
See here for ListViews in Xamarin.Forms.
You have not added any numbers in PhoneNumbers list. Add number first in PhoneNumbers list and then check.
public App()
{
InitializeComponent();
PhoneNumbers = new List<string>();
PhoneNumbers.Add("123456789");
PhoneNumbers.Add("178967897");
PhoneNumbers.Add("178945678");
MainPage = new NavigationPage(new MainPage());
}
I think you have forget to take input from user.So add this line in OnCall method
translatedNumber = PhonewordTranslator.ToNumber(phoneNumberText.Text);
Try this,
async void OnCall(object sender, EventArgs e)
{
translatedNumber = PhonewordTranslator.ToNumber(phoneNumberText.Text);
if (await this.DisplayAlert(
"Dial a Number",
"Would you like to call " + translatedNumber + "?",
"Yes",
"No"))
{
var dialer = DependencyService.Get<IDialer>();
if (dialer != null)
{
App.PhoneNumbers.Add(translatedNumber);
callHistoryButton.IsEnabled = true;
dialer.Dial(translatedNumber);
}
}
}

How to underline label with underline effect in Xamarin Forms?

I followed this tutorial to create underline effect. However, when my page starts it breaks without exception being caught. Has anyone managed to create underline effect? Here is a code:
UnderlineEffect.cs:
namespace XX.CustomForms
{
public class UnderlineEffect : RoutingEffect
{
public const string EffectNamespace = "XX.CustomForms";
public UnderlineEffect() : base($"{EffectNamespace}.{nameof(UnderlineEffect)}")
{
}
}
}
UnderlineLabel_Droid.cs:
[assembly: ResolutionGroupName(UnderlineEffect.EffectNamespace)]
[assembly: ExportEffect(typeof(UnderlineEffect), nameof(UnderlineEffect))]
namespace XX.Droid.Renderers
{
public class UnderlineEffect : PlatformEffect
{
protected override void OnAttached()
{
SetUnderline(true);
}
protected override void OnDetached()
{
SetUnderline(false);
}
protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs args)
{
base.OnElementPropertyChanged(args);
if (args.PropertyName == Label.TextProperty.PropertyName || args.PropertyName == Label.FormattedTextProperty.PropertyName)
{
SetUnderline(true);
}
}
private void SetUnderline(bool underlined)
{
try
{
var textView = (TextView)Control;
if (underlined)
{
textView.PaintFlags |= PaintFlags.UnderlineText;
}
else
{
textView.PaintFlags &= ~PaintFlags.UnderlineText;
}
}
catch (Exception ex)
{
Console.WriteLine("Cannot underline Label. Error: ", ex.Message);
}
}
}
}
And my xaml:
xmlns:custom="clr-namespace:XX.CustomForms;assembly=XX"
<Label Text="Privacy Notice" HorizontalTextAlignment="Center" >
<Label.Effects>
<custom:UnderlineEffect />
</Label.Effects>
</Label>
Xamarin Forms added a TextDecorations property to Labels. Update to Xamarin Forms 3.3.0+ and just set:
C#
Label label = new Label {
TextDecorations = TextDecorations.Underline
}
XAML
<Label TextDecorations="Underline"/>
Docs Link
Be aware that there was a bug on iOS when an underlined Label is in a ListView. Looks like it has been fixed and released in 3.5.0. I am still using a custom renderer on iOS for now until I am ready to update to the latest version.
GitHub issue
So continue using the iOS effect if you have not updated to XF 3.5.0 yet.
The lengths some people are going to to get underlined text in Xamarin is insane. Here's a way to do it without a thousand line custom renderer. The negative margin trick came from this guy.
<StackLayout HorizontalOptions="Start">
<Label Text="Underlined Text" />
<BoxView HeightRequest="1" BackgroundColor="Purple" HorizontalOptions="FillAndExpand" Margin="0,-7,0,0" />
</StackLayout>
Use TextDecorations property in Label class.
<Label Text="Underlined Text" TextDecorations="Underline"/>
To be able to add an underline to a label, we created custom renderers that inherits from Label.
public class CustomLabel : Label
{
public static readonly BindableProperty IsUnderlinedProperty = BindableProperty.Create("IsUnderlined", typeof(bool), typeof(CustomLabel), false);
public bool IsUnderlined
{
get { return (bool) GetValue(IsUnderlinedProperty); }
set { SetValue(IsUnderlinedProperty, value); }
}
}
In your xaml page you can use it as:
<s:CustomLabel IsUnderlined="True" Text="UnderlinedText" FontSize="18" HorizontalOptions="CenterAndExpand">
Note that s is the namespace declared in the root element of xaml page.
Now your renderer in Android would be something like that:
public class CustomLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null && Element != null)
{
if (((CustomLabel)Element).IsUnderlined)
{
Control.PaintFlags = PaintFlags.UnderlineText;
}
}
}
}

Resources