Bind height of BoxView or Frame to (hidden) NavigationBar in Xamarin - xamarin

Instead of having the standard NavigationBar on the mainpage, I want to hide that one and load a custom one. I think using a BoxView (edit:) frame is the best method for this. However, How do I bind the height(request) of a BoxView to a (hidden) NavigationBar, preferably in Xaml?
(frame of boxview should have the same method)

For two common controls, you can bind a height of one control to another control like this(take BoxView and a Button as an example):
<BoxView Color="Yellow"
x:Name="testbox"
WidthRequest="160"
HeightRequest="60"
VerticalOptions="Center"
HorizontalOptions="Center" />
<Button Text="test..."
BindingContext="{x:Reference testbox}"
WidthRequest="160"
HeightRequest="{Binding Path=HeightRequest}"/>
Update
You can get the NavigationBar height on each platform using custom renderer.
And Then binding the height of NavigationBar to Boxview by following the method above.
For Android:
public class NaviRendererForAndroid : NavigationPageRenderer
{
public CustomNaviForAndroid(Context context) : base(context)
{
}
protected override void
OnElementChanged(ElementChangedEventArgs<NavigationPage> e)
{
base.OnElementChanged(e);
var height = 0;
Resources resources = Context.Resources;
int resourceId = resources.GetIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0)
{
height = resources.GetDimensionPixelSize(resourceId);
}
}
}
For IOS:
public class NaviRendererForiOS : NavigationRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
var height = NavigationBar.Bounds.Height;
}
}

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. ...
}
...
}

Xamarin.Forms ScrollView - animate its growth as it expands

We've got a ScrollView which I've set with a VerticalOptions of "End", so that when we add content to it at runtime it 'grows' from the bottom.
We're scrolling to the end when adding content, with animation. This looks good when the ScrollView is full and is actually scrolling.
However, when content is added to the ScrollView, the new content appears immediately with no animation.
Any thoughts on how to animate the growth of the ScrollView as the new content is added? Ideally I'd like it to slide up, like the animated scroll when it's full.
We're using a RepeaterView as the content of the ScrollView, if that's relevant.
Relevant existing code below (we're using Forms with MvvmCross - hence an MVVM pattern):
ViewModel
private async Task NextClick()
{
var claimFlowQuestion = await GetClaimFlowQuestion(_currentIndexQuestion);
Questions.Add(ClaimFlowExtendFromClaimFlow(claimFlowQuestion));
// Trigger PropertyChanged so the Repeater updates
await RaisePropertyChanged(nameof(Questions));
// Trigger the QuestionAdded event so the ScrollView can scroll to the bottom (initiated in the xaml.cs code behind)
QuestionAdded?.Invoke(this, new EventArgs());
}
XAML
<ScrollView Grid.Row="1" x:Name="QuestionScrollView">
<StackLayout VerticalOptions="End"
Padding ="10,0,10,0"
IsVisible="{Binding Busy, Converter={StaticResource InvertedBooleanConvertor}}}">
<controls:RepeaterView
x:Name="QuestionRepeater"
Margin ="10"
AutomationId="IdQuestions"
Direction ="Column"
ItemsSource ="{Binding Questions}"
ClearChild ="false">
<controls:RepeaterView.ItemTemplate>
<DataTemplate>
<ViewCell>
<controlsClaimFlowControls:QuestionBlock
Margin ="0,20,0,20"
QuestionNumber ="{Binding Index}"
QuestionText ="{Binding QuestionText}"
QuestionDescription="{Binding QuestionDescription}"
ItemsSource ="{Binding Source}"
DisplayMemberPath ="{Binding DisplayPaths}"
QuestionType ="{Binding QuestionType}"
SelectedItem ="{Binding Value}"
IsEnabledBlock ="{Binding IsEnabled}" />
</ViewCell>
</DataTemplate>
</controls:RepeaterView.ItemTemplate>
</controls:RepeaterView>
</StackLayout>
</ScrollView>
XAML.cs
protected override void OnAppearing()
{
if (BindingContext != null)
{
MedicalClaimConditionPageModel model = (MedicalClaimConditionPageModel)this.BindingContext.DataContext;
model.QuestionAdded += Model_QuestionAdded;
}
base.OnAppearing();
}
protected override void OnDisappearing()
{
MedicalClaimConditionPageModel model = (MedicalClaimConditionPageModel)this.BindingContext.DataContext;
model.QuestionAdded -= Model_QuestionAdded;
base.OnDisappearing();
}
void Model_QuestionAdded(object sender, EventArgs e)
{
Device.BeginInvokeOnMainThread(async () =>
{
if (Device.RuntimePlatform == Device.iOS)
{
// We need a Task.Delay to allow the contained controls to repaint and have their new sizes
// Ultimately we should come up with a better resolution - the delay value can vary depending on device and OS version.
await Task.Delay(50);
}
await QuestionScrollView.ScrollToAsync(QuestionRepeater, ScrollToPosition.End, true);
});
}
A reasonable answer has been posted by user yelinzh on the Xamarin forums:
How about trying to use Custom Renderer such as the fading effect.
[assembly: ExportRenderer(typeof(ScrollView_), typeof(ScrollViewR))]
namespace App3.Droid
{
public class ScrollViewR : ScrollViewRenderer
{
public ScrollViewR(Context context) : base(context)
{
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
this.SetFadingEdgeLength(300);
this.VerticalFadingEdgeEnabled = true;
}
}
}
This would probably work. In fact we've got for a different approach
(after feedback from the customer), where we're keeping the current question at the top of the screen and scrolling off upwards, so the content is no longer expanding.

Xamarin.Form iOS custom height ProgressBar issue

UPDATED:
So, I'm unable to create IOS custom height ProgressBar.
I use the latest version of Xamarin.Forms.
.cs file:
public class SplashScreenProgressBar : ProgressBar
{
public static readonly BindableProperty TintColorProperty =
BindableProperty.Create<CustomProgressBar, Color>( p => p.TintColor, Color.Green);
public Color TintColor
{
get { return (Color) GetValue(TintColorProperty); }
set { SetValue(TintColorProperty, value); }
}
public static readonly BindableProperty HeightExtendedProperty =
BindableProperty.Create("HeightExtended", typeof(double), typeof(SplashScreenProgressBar), 10.0);
public double HeightExtended
{
get { return (double) GetValue(HeightExtendedProperty); }
set { SetValue(HeightExtendedProperty, value); }
}
public static readonly BindableProperty BackgroundColorExtendedProperty =
BindableProperty.Create("BackgroundColorExtended", typeof(Color), typeof(SplashScreenProgressBar),
Color.White);
public Color BackgroundColorExtended
{
get { return (Color) GetValue(BackgroundColorExtendedProperty); }
set { SetValue(BackgroundColorExtendedProperty, value); }
}
}
Here is iOS renderer:
public class SplashScreenProgressBarRenderer : ProgressBarRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<ProgressBar> e)
{
base.OnElementChanged(e);
var element = (SplashScreenProgressBar)Element;
this.Control.ProgressTintColor = element.TintColor.ToUIColor();
this.Control.TrackTintColor = element.BackgroundColorExtended.ToUIColor();
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
var element = (SplashScreenProgressBar)Element;
var X = 1.0f;
var Y = (System.nfloat)element.HeightExtended;
CGAffineTransform transform = CGAffineTransform.MakeScale(X, Y);
this.Control.Transform = transform;
this.Control.ClipsToBounds = true;
this.Control.Layer.MasksToBounds = true;
this.Control.CornerRadius = 5;
}
}
xaml file:
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" BackgroundColor="White" >
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Margin="50" BackgroundColor="White" >
<views:SplashScreenProgressBar x:Name="Progress"
TintColor="#5FA5F9"
HorizontalOptions="FillAndExpand"
VerticalOptions="Center"
BackgroundColorExtended="#FFF" />
</StackLayout>
</StackLayout>
But this way doesn't work.
I googled and tried almost all samples which I've found, but nothing happened.
Screenshot:
As you see on the screenshot corner radius is applied to ProgressBar, but height(scale) isn't applied.
In PCL
StackLayout is overlapped with status bar.
Add Margin on it.
<StackLayout Margin="50" xxxxx
In Renderer
ClipsToBounds ,Layer.MasksToBounds ,Layer.CornerRadius should be set on the Control not the Renderer
this.Control.Transform = transform;
this.Control.ClipsToBounds = true;
this.Control.Layer.MasksToBounds = true;
this.Control.Layer.CornerRadius = 5;
When use the custom renderer in ios, it always occupy the whole area of parent element. So, u need to update the progress bar frame once again in layoutsubview.
bool is rendered;
public override void LayoutSubviews()
{
if(!rendered)
{
Frame = new CGRect(x,y,width,height);
setNeedsdisplay
}
rendered=true;
}
So, I spent a lot of hours to researching and investigation.
And seems there xamarin.forms iOS bug for progress bar rounded corners.

How can I set up different footers for TableSections when using a Custom TableView Renderer

I am using a renderer to allow me to set a custom footer in my TableView. The renderer works but I would like to have the capability to set up different footers for the different table sections. For example one footer for table section 0 and another for table section 1, all the way up to table section 5.
Here's the XAML that I am using:
<!-- <local:ExtFooterTableView x:Name="tableView" Intent="Settings" HasUnevenRows="True">-->
<TableView x:Name="tableView" Intent="Settings" HasUnevenRows="True">
<TableSection Title="Cards1">
<ViewCell Height="50">
<Label Text="Hello1" />
</ViewCell>
<ViewCell Height="50">
<Label Text="Hello2" />
</ViewCell>
</TableSection>
<TableSection Title="Cards2">
<TextCell Height="50" Text="Hello"></TextCell>
</TableSection>
</TableSection>
<!-- </local:ExtFooterTableView>-->
</TableView>
and here is the C# class and renderer:
public class ExtFooterTableView : TableView
{
public ExtFooterTableView()
{
}
}
and:
using System;
using Japanese;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(ExtFooterTableView), typeof(Japanese.iOS.ExtFooterTableViewRenderer))]
namespace Japanese.iOS
{
public class ExtFooterTableViewRenderer : TableViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
var tableView = Control as UITableView;
var formsTableView = Element as TableView;
tableView.WeakDelegate = new CustomFooterTableViewModelRenderer(formsTableView);
}
private class CustomFooterTableViewModelRenderer : TableViewModelRenderer
{
public CustomFooterTableViewModelRenderer(TableView model) : base(model)
{
}
public override UIView GetViewForFooter(UITableView tableView, nint section)
{
Debug.WriteLine("xx");
if (section == 0)
{
return new UILabel()
{
// Text = TitleForFooter(tableView, section), // or use some other text here
Text = "abc",
TextAlignment = UITextAlignment.Left
// TextAlignment = NSTextAlignment.NSTextAlignmentJustified
};
}
else
{
return new UILabel()
{
// Text = TitleForFooter(tableView, section), // or use some other text here
Text = "def",
TextAlignment = UITextAlignment.Left
// TextAlignment = NSTextAlignment.NSTextAlignmentJustified
};
}
}
}
}
}
The code works but I would like to find out how I can set up a different footer text for different sections in the XAML. Something like this:
From what I see it looks like the code is partly there TitleForFooter(tableView, section) but I am not sure how to use it and how I could set it up. Note that I am not really looking for a view model solution. I would be happy to be simply able to specify the section footer text as part of the TableView XAML.
I'd appreciate if anyone could give me some advice on this.
First of all, in order to be able to specify the section footer text in XAML - simplest option would be to create a bindable property in TableSection. But as TableSection is sealed, we can't derive it to define our custom bindable properties.
So, the next option is to create a attached bindable property.
public class Ex
{
public static readonly BindableProperty FooterTextProperty =
BindableProperty.CreateAttached("FooterText", typeof(string), typeof(Ex), defaultValue: default(string));
public static string GetFooterText(BindableObject view)
{
return (string)view.GetValue(FooterTextProperty);
}
public static void SetFooterText(BindableObject view, string value)
{
view.SetValue(FooterTextProperty, value);
}
}
Next step would be to update renderer to retrieve this value for every section:
private class CustomFooterTableViewModelRenderer : TableViewModelRenderer
{
public CustomFooterTableViewModelRenderer(TableView model) : base(model)
{
}
public override UIView GetViewForFooter(UITableView tableView, nint section)
{
return new UILabel()
{
Text = TitleForFooter(tableView, section), // or use some other text here
Font = UIFont.SystemFontOfSize(14),
ShadowColor = Color.White.ToUIColor(),
ShadowOffset = new CoreGraphics.CGSize(0, 1),
TextColor = Color.DarkGray.ToUIColor(),
BackgroundColor = Color.Transparent.ToUIColor(),
Opaque = false,
TextAlignment = UITextAlignment.Center
};
}
//Retrieves the footer text for corresponding section through the attached property
public override string TitleForFooter(UITableView tableView, nint section)
{
var tblSection = View.Root[(int)section];
return Ex.GetFooterText(tblSection);
}
}
Sample Usage
<local:ExtFooterTableView x:Name="tableView" Intent="Settings" HasUnevenRows="True">
<TableSection Title="Cards1" local:Ex.FooterText="Sample description">
<ViewCell Height="50">
<Label Margin="20,0,20,0" Text="Hello1" />
</ViewCell>
<ViewCell Height="50">
<Label Margin="20,0,20,0" Text="Hello2" />
</ViewCell>
</TableSection>
<TableSection Title="Cards2" local:Ex.FooterText="Disclaimer note">
<TextCell Height="50" Text="Hello"></TextCell>
</TableSection>
</local:ExtFooterTableView>
It is very simple. you need to add the bindable property for pass value from XAML to CustomRenderer in CustomControl like this:
Customer TableView
public class ExtFooterTableView : TableView
{
public ExtFooterTableView()
{
}
}
Xaml control code
<local:ExtFooterTableView x:Name="tableView" Intent="Settings" HasUnevenRows="True">
Renderer class
using System;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using yournamespace;
using System.ComponentModel;
[assembly: ExportRenderer(typeof(ExtFooterTableView), typeof(FooterTableViewRenderer))]
namespace yournamespace
{
public class FooterTableViewRenderer : TableViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
{
base.OnElementChanged(e);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var view = (ExtFooterTableView)Element;
if (e.PropertyName == ExtFooterTableView.IntentProperty.PropertyName)
{
string intent = view.Intent;
// Do your stuff for intent property
}
if (e.PropertyName == ExtFooterTableView.HasUnevenRowsProperty.PropertyName)
{
bool hasUnevenRows = view.HasUnevenRows;
// Do yout stuff for HasUnevenRow
}
}
}
}

Xamarin WebView scale to fit

Is there a way to set a WebView in Xamarin to scale its content to fit the screen by default and still allow for pinch zooming in/out?
We're going to use this to display documents we have online.
I solved the scale fit to page and zooming by Custom Renderers, as given below
For IOS
public class CustomWebViewRenderer : WebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
var view = Element as CustomWebView;
if (view == null || NativeView == null)
{
return;
}
this.ScalesPageToFit = true;
}
}
For Android
public class CustomWebViewRenderer : WebViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.Settings.BuiltInZoomControls = true;
Control.Settings.DisplayZoomControls = false;
Control.Settings.LoadWithOverviewMode = true;
Control.Settings.UseWideViewPort = true;
}
}
}
If you're using Xamarin.Forms that would look something like this
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="WebViewDemo.LoadingDemo" Title="Loading Demo">
<ContentPage.Content>
<StackLayout>
<WebView x:Name="Browser"
VerticalOptions = LayoutOptions.FillAndExpand ,
HorizontalOptions = LayoutOptions.FillAndExpand,
</StackLayout>
</ContentPage.Content>
</ContentPage>
This should fill the page and not affect pinch and zoom.

Resources