Is there a better way to set TabbedPage bottom tab bar height than in ViewModel? - xamarin

I want to change the height of the bottom TabBar in my Xamarin app. Now I am doing this via ViewModel properties:
public partial class MainPage : TabbedPage
{
public int TabBarHeight
{
get { return _tabBarHeight; }
set { _tabBarHeight = value; OnPropertyChanged(); }
}
int _tabBarHeight = 200;
And a custom renderer for iOS:
using System;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using System.Diagnostics;
using Ja.Enums;
using System.ComponentModel;
using CoreGraphics;
[assembly: ExportRenderer(typeof(TabbedPage), typeof(Ja.iOS.TabbedPageRenderer))]
namespace Ja.iOS
{
public class TabbedPageRenderer : TabbedRenderer
{
private MainPage _page;
public TabbedPageRenderer()
{
this.ViewControllerSelected += OnTabbarControllerItemSelected;
}
public override void ViewWillLayoutSubviews()
{
base.ViewWillLayoutSubviews();
TabBar.Frame = new CGRect(TabBar.Frame.X, TabBar.Frame.Y + (TabBar.Frame.Height - _page.TabBarHeight), TabBar.Frame.Width, _page.TabBarHeight);
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
e.OldElement.PropertyChanged -= Current_PropertyChanged;
return;
}
if (e.NewElement != null)
{
_page = (MainPage)e.NewElement;
e.NewElement.PropertyChanged += Current_PropertyChanged;
}
}
void Current_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "FrameHeight")
ViewWillLayoutSubviews();
}
}
}
Is this an optimal way to be doing this? I seem to remember having a shared class. That class would contain the code that's in the first few lines of my main as bound elements.
Could anyone comment on this and perhaps suggest a better way of doing this.

Looks like something I would use a Custom Control for (which is what I think you mean with 'shared class').
First you create the class below in your PCL. A CustomTabbedPage, which inherits from TabbedPage but has one extra property: 'TabBarHeight'
public class CustomTabbedPage : TabbedPage {
public static readonly BindableProperty TabBarHeightProperty = BindableProperty.Create("TabBarHeight", typeof(int), typeof(TabbedPage), 0);
public int TabBarHeight {
get { return (int)GetValue(TabBarHeightProperty); }
set { SetValue(TabBarHeightProperty, value); }
}
}
Now edit your renderer to a renderer of the CustomTabbedPage. Then you can easily access the TabBarHeight property by using this.Element.TabBarHeight.
using System;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using System.Diagnostics;
using Ja.Enums;
using System.ComponentModel;
using CoreGraphics;
[assembly: ExportRenderer(typeof(CustomTabbedPage), typeof(Ja.iOS.CustomTabbedPageRenderer))]
namespace Ja.iOS
{
public class CustomTabbedPageRenderer : TabbedRenderer
{
public TabbedPageRenderer()
{
this.ViewControllerSelected += OnTabbarControllerItemSelected;
}
public override void ViewWillLayoutSubviews()
{
base.ViewWillLayoutSubviews();
TabBar.Frame = new CGRect(TabBar.Frame.X, TabBar.Frame.Y + (TabBar.Frame.Height - this.Element.TabBarHeight), TabBar.Frame.Width, this.Element.TabBarHeight);
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.OldElement != null) {
e.OldElement.PropertyChanged -= Element_PropertyChanged;
}
if (e.NewElement != null) {
e.NewElement.PropertyChanged += Element_PropertyChanged;
}
}
void Current_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "FrameHeight")
ViewWillLayoutSubviews();
}
}
}
There might be some syntax errors in there but you get the point.
Shoot if you have any questions.

Related

Unable to get the trigger button event call back from Web view for Xamarin using hybridWebView

I have my mobile application build using Xamarin forms, which consist of a webView loaded into my application. The WebView Consist of buttons and icons being loaded. I have a requirement to trigger the button event Listener , so that I can do the corresponding functionality. Can anyone help me in how to get the button events being called in Xamarin forms. I have used HybridWebView as mentioned in the documents and sample ,but nothing seems to be working for me. Below is the code I have used for the same.
code:
HybridWebViewRenderer
using System;
using Android.Content;
using TestProject.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(Vinspector.HybridWebView), typeof(HybridWebViewRenderer))]
namespace TestProject.Droid
{
public class HybridWebViewRenderer : WebViewRenderer
{
const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);};";
Context _context;
public HybridWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
Console.WriteLine("print inside control is null!!!");
var webView = new Android.Webkit.WebView(_context);
webView.Settings.JavaScriptEnabled = true;
webView.Settings.DatabaseEnabled = true;
webView.Settings.DomStorageEnabled = true;
SetNativeControl(webView);
}
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup();
}
if (e.NewElement != null)
{
Control.Settings.JavaScriptEnabled = true;
Control.Settings.DatabaseEnabled = true;
Control.Settings.DomStorageEnabled = true;
Control.SetWebViewClient(new JavascriptWebViewClient("javascript:(function getCloseCa(){var btns = document.getElementById('dvCloseBtn');})()"));
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.LoadUrl(“www.google.com”);
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
((HybridWebView)Element).Cleanup();
}
base.Dispose(disposing);
}
}
}
Code For JavascriptWebViewClient
namespace TestProject.Droid
{
public class JavascriptWebViewClient : WebViewClient
{
string _javascript;
public JavascriptWebViewClient(string javascript)
{
_javascript = javascript;
}
public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
base.OnPageFinished(view, url);
if (Build.VERSION.SdkInt >= BuildVersionCodes.Kitkat)
{
view.EvaluateJavascript(_javascript, null);
}
else {
view.LoadUrl(_javascript);
}
}
}
}
Code For JsBridge
namespace TestProject.Droid
{
public class JSBridge : Java.Lang.Object
{
readonly WeakReference<HybridWebViewRenderer> hybridWebViewRenderer;
public JSBridge(HybridWebViewRenderer hybridRenderer)
{
hybridWebViewRenderer = new WeakReference<HybridWebViewRenderer>(hybridRenderer);
}
[JavascriptInterface]
[Export("invokeAction")]
public void InvokeAction(string data)
{
HybridWebViewRenderer hybridRenderer;
if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
{
((HybridWebView)hybridRenderer.Element).InvokeAction(data);
}
}
}
}
WebPage.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:TestProject."
x:Class="TestProject..WebPage">
<StackLayout>
<ProgressBar Progress="0.2"
HorizontalOptions="FillAndExpand"
x:Name="progress"
IsVisible="True"/>
<local:HybridWebView
x:Name="hybridWebView"
HeightRequest="1000"
WidthRequest="1000"
Navigating="OnNavigating"
Navigated="OnNavigated"
VerticalOptions="FillAndExpand"
>
</local:HybridWebView>
</StackLayout>
</ContentPage>
WebPage.Xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Vinspector.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Newtonsoft.Json;
using TestProject.Models;
namespace TestProject
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class WebPage : ContentPage
{
public WebPage()
{
InitializeComponent();
hybridWebView.RegisterAction(data => Console.WriteLine("value of data is" + data));
}
protected async override void OnAppearing()
{
base.OnAppearing();
await progress.ProgressTo(0.9, 900, Easing.SpringIn);
}
protected void OnNavigating(object sender, WebNavigatingEventArgs e)
{
progress.IsVisible = true;
Console.WriteLine(" Result OnNavigating....");
}
protected void OnNavigated(object sender, WebNavigatedEventArgs e)
{
progress.IsVisible = false;
Console.WriteLine(" Result OnNavigated....");
var retorno = hybridWebView.EvaluateJavaScriptAsync("common.GetCurrentUserID();");
covertAsync(retorno);
}
private async Task covertAsync(Task<string> retorno)
{
string userID = await retorno;
string AccessToken = LoadApplicationProperty<string>("access_token");
Console.WriteLine(" Result UserID...." + userID + " " + AccessToken);
}
private T LoadApplicationProperty<T>(string key)
{
return (T)Xamarin.Forms.Application.Current.Properties[key];
}
}
}
You can use the javascript function like the below code to access the buttons and events clicked from webview:
using System;
using Android.Content;
using TestProject.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(Vinspector.HybridWebView), typeof(HybridWebViewRenderer))]
namespace TestProject.Droid
{
public class HybridWebViewRenderer : WebViewRenderer
{
const string JavascriptFunction= "document.getElementById('btnId').onclick= function(data){jsBridge.submissionAction(data);}";`
Context _context;
public HybridWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
Console.WriteLine("print inside control is null!!!");
var webView = new Android.Webkit.WebView(_context);
webView.Settings.JavaScriptEnabled = true;
webView.Settings.DatabaseEnabled = true;
webView.Settings.DomStorageEnabled = true;
SetNativeControl(webView);
}
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup();
}
if (e.NewElement != null)
{
Control.Settings.JavaScriptEnabled = true;
Control.Settings.DatabaseEnabled = true;
Control.Settings.DomStorageEnabled = true;
webView.SetWebViewClient(new `JavascriptWebViewClient(this,string.Format("javascript: {0}", JavascriptFunction)));`
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.LoadUrl(“www.google.com”);
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
((HybridWebView)Element).Cleanup();
}
base.Dispose(disposing);
}
}
}

SwitchRender.SwitchRender()' is obsolete with Xamarin.Forms renderer

I am implementing code for SwitchRenderer in Android but it appears that there is an issue with the code as I get a warning:
'SwitchRender.SwitchRender()' is obsolete; 'This constuctor is
obsolets as of version 2.5. Please use SwitchRender(Context) instead.'
Does anyone have any ideas how I can resolve this problem:
using Japanese.Android;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Japanese;
[assembly: ExportRenderer(typeof(ExtSwitch), typeof(ExtSwitchRenderer))]
namespace Japanese.Android
{
public class ExtSwitchRenderer : SwitchRenderer
{
private ExtSwitch view;
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Switch> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || e.NewElement == null)
return;
view = (ExtSwitch)Element;
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.JellyBean)
{
if (this.Control != null)
{
if (this.Control.Checked)
{
this.Control.TrackDrawable.SetColorFilter(view.SwitchOnColor.ToAndroid(), PorterDuff.Mode.SrcAtop);
}
else
{
this.Control.TrackDrawable.SetColorFilter(view.SwitchOffColor.ToAndroid(), PorterDuff.Mode.SrcAtop);
}
this.Control.CheckedChange += this.OnCheckedChange;
UpdateSwitchThumbImage(view);
}
//Control.TrackDrawable.SetColorFilter(view.SwitchBGColor.ToAndroid(), PorterDuff.Mode.Multiply);
}
}
private void UpdateSwitchThumbImage(CustomSwitch view)
{
if (!string.IsNullOrEmpty(view.SwitchThumbImage))
{
view.SwitchThumbImage = view.SwitchThumbImage.Replace(".jpg", "").Replace(".png", "");
int imgid = (int)typeof(Resource.Drawable).GetField(view.SwitchThumbImage).GetValue(null);
Control.SetThumbResource(Resource.Drawable.icon);
}
else
{
Control.ThumbDrawable.SetColorFilter(view.SwitchThumbColor.ToAndroid(), PorterDuff.Mode.Multiply);
// Control.SetTrackResource(Resource.Drawable.track);
}
}
private void OnCheckedChange(object sender, CompoundButton.CheckedChangeEventArgs e)
{
if (this.Control.Checked)
{
this.Control.TrackDrawable.SetColorFilter(view.SwitchOnColor.ToAndroid(), PorterDuff.Mode.SrcAtop);
}
else
{
this.Control.TrackDrawable.SetColorFilter(view.SwitchOffColor.ToAndroid(), PorterDuff.Mode.SrcAtop);
}
}
protected override void Dispose(bool disposing)
{
this.Control.CheckedChange -= this.OnCheckedChange;
base.Dispose(disposing);
}
}
}
Check out the answer here. The new way to do Android custom renderers would be this:
public class ExtSwitchRenderer : SwitchRenderer
{
private ExtSwitch view;
public ExtSwitchRenderer(Context context) : base(context) { }
....
}
Then if you actually need to use the Android context in your custom renderer, you would use the one passed in through the constructor, instead of Forms.Context for example.

Is there a way to recognize a short or long press on a screen with Xamarin.Forms?

I have an application that responds to a short tap on the screen. I do this by adding a gesture recognizer.
Is there a way that I can make it respond to either a short or a long press and have these call different methods?
You will have implement renderers for that. In case of iOS you can use UILongPressGestureRecognizer to detect a long-press action, while in case of Android, you can use GestureDetector to do the same.
Forms control
public class CustomView : ContentView
{
public event EventHandler<EventArgs> LongPressEvent;
public void RaiseLongPressEvent()
{
if (IsEnabled)
LongPressEvent?.Invoke(this, EventArgs.Empty);
}
}
iOS renderer
[assembly: ExportRenderer(typeof(CustomView), typeof(CustomViewRenderer))]
namespace AppNamespace.iOS
{
public class CustomViewRenderer : ViewRenderer<CustomView, UIView>
{
UILongPressGestureRecognizer longPressGestureRecognizer;
protected override void OnElementChanged(ElementChangedEventArgs<CustomView> e)
{
longPressGestureRecognizer = longPressGestureRecognizer ??
new UILongPressGestureRecognizer(() =>
{
Element.RaiseLongPressEvent();
});
if (longPressGestureRecognizer != null)
{
if (e.NewElement == null)
{
this.RemoveGestureRecognizer(longPressGestureRecognizer);
}
else if (e.OldElement == null)
{
this.AddGestureRecognizer(longPressGestureRecognizer);
}
}
}
}
}
Android renderer
[assembly: ExportRenderer(typeof(CustomView), typeof(CustomViewRenderer))]
namespace AppNamespace.Droid
{
public class CustomViewRenderer : ViewRenderer<CustomView, Android.Views.View>
{
private CustomViewListener _listener;
private GestureDetector _detector;
public CustomViewListener Listener
{
get
{
return _listener;
}
}
public GestureDetector Detector
{
get
{
return _detector;
}
}
protected override void OnElementChanged(ElementChangedEventArgs<CustomView> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
GenericMotion += HandleGenericMotion;
Touch += HandleTouch;
_listener = new CustomViewListener(Element);
_detector = new GestureDetector(_listener);
}
}
protected override void Dispose(bool disposing)
{
GenericMotion -= HandleGenericMotion;
Touch -= HandleTouch;
_listener = null;
_detector?.Dispose();
_detector = null;
base.Dispose(disposing);
}
void HandleTouch(object sender, TouchEventArgs e)
{
_detector.OnTouchEvent(e.Event);
}
void HandleGenericMotion(object sender, GenericMotionEventArgs e)
{
_detector.OnTouchEvent(e.Event);
}
}
public class CustomViewListener : GestureDetector.SimpleOnGestureListener
{
readonly CustomView _target;
public CustomViewListener(CustomView s)
{
_target = s;
}
public override void OnLongPress(MotionEvent e)
{
_target.RaiseLongPressEvent();
base.OnLongPress(e);
}
}
}
Sample Usage
<local:CustomView LongPressEvent="Handle_LongPress" />
Code-behind
void Handle_LongPressEvent(object sender, System.EventArgs e)
{
//handle long press event here
}
You can also customize above to add a command to make it more MVVM friendly.
You can refer this link for more details regarding gesture recognizers.
http://arteksoftware.com/gesture-recognizers-with-xamarin-forms/
You will have implement renderers for that. In case ios and android
best way for do that!

Customizing the progress bar color in UWP using Xamarin Forms

I made a progress bar customization (custom renderer) to change the progress bar color in iOS and Droid as seen in the following
Custom progress bar class in the PCL:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace App2
{
public class ColorProgressBar : ProgressBar
{
public static BindableProperty BarColorProperty
= BindableProperty.Create<ColorProgressBar, Color>(p =>
p.BarColor, default(Color));
public Color BarColor
{
get { return (Color)GetValue(BarColorProperty); }
set { SetValue(BarColorProperty, value); }
}
}
}
Custom renderer for iOS:
using App2;
using App2.iOS;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using Xamarin.Forms;
//using MonoTouch.Foundation;
//using MonoTouch.UIKit;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(ColorProgressBar),
typeof(ColorProgressBarRenderer))]
namespace App2.iOS
{
public class ColorProgressBarRenderer : ProgressBarRenderer
{
public ColorProgressBarRenderer()
{ }
protected override void
OnElementChanged(ElementChangedEventArgs<ProgressBar> e)
{
base.OnElementChanged(e);
if (e.NewElement == null)
return;
if (Control != null)
{
UpdateBarColor();
}
}
protected override void OnElementPropertyChanged(object sender,
PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName ==
ColorProgressBar.BarColorProperty.PropertyName)
{
UpdateBarColor();
}
}
private void UpdateBarColor()
{
var element = Element as ColorProgressBar;
Control.TintColor = element.BarColor.ToUIColor();
}
}
}
Custom renderer for Android:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Renderscripts;
using static Java.Util.ResourceBundle;
using Xamarin.Forms.Platform.Android;
using Android.Graphics;
using System.ComponentModel;
using Xamarin.Forms;
using App2;
using App2.Droid;
[assembly: ExportRenderer(typeof(ColorProgressBar),
typeof(ColorProgressBarRenderer))]
namespace App2.Droid
{
public class ColorProgressBarRenderer : ProgressBarRenderer
{
public ColorProgressBarRenderer()
{ }
protected override void
OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ProgressBar>
e)
{
base.OnElementChanged(e);
if (e.NewElement == null)
return;
if (Control != null)
{
UpdateBarColor();
}
}
protected override void OnElementPropertyChanged(object sender,
PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName ==
ColorProgressBar.BarColorProperty.PropertyName)
{
UpdateBarColor();
}
}
private void UpdateBarColor()
{
var element = Element as ColorProgressBar;
// http://stackoverflow.com/a/29199280
Control.IndeterminateDrawable.SetColorFilter(
element.BarColor.ToAndroid(), PorterDuff.Mode.SrcIn);
Control.ProgressDrawable.SetColorFilter(
element.BarColor.ToAndroid(), PorterDuff.Mode.SrcIn);
}
}
}
I'm setting the custom progress bar's color this way:
var progressBar = new ColorProgressBar();
progressBar.BarColor = Color.Red;
I don't understand how to make a custom renderer class for UWP that changes the color of the progress bar. Could someone please help me understand how to do this class?
You're going to want to update the Foreground property of the native Windows.UI.Xaml.Controls.ProgressBar control to change the color.
It should look something like this:
private void UpdateBarColor()
{
var element = Element as ColorProgressBar;
Control.Foreground = new Windows.UI.Xaml.Media.SolidColorBrush(
GetWindowsColor(element.BarColor));
}
Windows.UI.Color GetWindowsColor(Color color)
{
return Windows.UI.Color.FromArgb((byte)(255 * color.A), (byte)(255 * color.R), (byte)(255 * color.G), (byte)(255 * color.B));
}
This will take your BarColor, use it to make a SolidColorBrush of the right color, and then assign that to your native ProgressBar control.

How to create an Entry renderer in Xamarin?

I want to create a renderer for an entry field using Xamarin. What is the best way to get it? I have no idea how to get it, any help will be appreciate.
This is my entry field and I want to create font size renderer for that
<Entry x:Name="txtTest"/>
This is sample of Entry that set padding to 0 and can set TextSize property.
namespace projectname
{
public class BareEntry : Entry
{
public BareEntry ()
{
}
public static readonly BindableProperty TextSizeProperty =
BindableProperty.Create<BareEntry,int> (X => X.TextSize, 10);
public int TextSize
{
get {return (int)base.GetValue (TextSizeProperty);}
set {base.SetValue (TextSizeProperty, value);}
}
}
}
This part is on android platform
using Xamarin.Forms;
using projectname;
using projectname.Droid;
using Xamarin.Forms.Platform.Android;
using MocMSearch.Droid;
using Android.Support.V4.Content;
[assembly: ExportRenderer (typeof (BareEntry), typeof (BareEntryRenderer))]
namespace MocMSearch.Droid
{
public class BareEntryRenderer : EntryRenderer
{
public BareEntryRenderer ()
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Entry> e)
{
base.OnElementChanged(e);
Recreate ();
}
void Recreate()
{
if (this.Control == null) return;
Control.SetPadding (0,0,0,0);
Control.SetTextSize (Android.Util.ComplexUnitType.Mm,(Element as BareEntry).TextSize);
}
protected override void OnElementPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged (sender, e);
if (e.PropertyName == "TextSize")
{
Recreate ();
this.Invalidate ();
}
}
}
}

Resources