How to ExportRenderer Multiple TabbedPage in Xamarin - xamarin

I'm wondering about the Custom TabbedPageRenderer issue. I have created a file MyTabbedPageRenderer.cs in Xamarin iOS. Everything I set up in this is fine. Until I used Plugin.Badge. I have installed and configured in Project Xamarin iOS: AssemblyInfo.cs add: [assembly: ExportRenderer(typeof(TabbedPage), typeof(BadgedTabbedPageRenderer))].
Yes, It does accept the Badge. However everything I set in: MyTabbedPageRenderer.cs becomes useless.
I try to move what is in BadgedTabbedPageRenderer.cs here into MyTabbedPageRenderer.cs. And I change [assembly: ExportRenderer(typeof(TabbedPage), typeof(BadgedTabbedPageRenderer))] to [assembly: ExportRenderer(typeof(TabbedPage), typeof(MyTabbedPageRenderer))]. However it still doesn't work.
MyTabbedPageRenderer.cs
[assembly: ExportRenderer(typeof(TabbedPage), typeof(MyTabbedPageRenderer))]
namespace xxxx.iOS
{
internal class MyTabbedPageRenderer : TabbedRenderer
{
private bool _initialized;
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
//TabBar.ClipsToBounds = true;
TabBar.TintColor = UIColor.Gray;
TabBar.BarTintColor = UIColor.White;
TabBar.BackgroundColor = UIColor.White;
MessagingCenter.Subscribe<object, int>(this, "Add", (obj, index) => {
TabBar.addItemBadge(index);
});
MessagingCenter.Subscribe<object, int>(this, "Remove", (obj, index) => {
TabBar.removeItemBadge(index);
});
}
public override void ViewWillAppear(bool animated)
{
if (!_initialized)
{
if (TabBar?.Items == null)
return;
foreach (var item in TabBar.Items)
{
item.Image = ScalingImageToSize(item.Image, new CGSize(20, 20)); // set the size here as you want
}
var tabs = Element as TabbedPage;
if (tabs != null)
{
for (int i = 0; i < TabBar.Items.Length; i++)
{
UpdateItem(TabBar.Items[i], tabs.Children[i].Icon, tabs.Children[i].StyleId);
}
}
_initialized = true;
}
base.ViewWillAppear(animated);
}
private void UpdateItem(UITabBarItem item, string icon, string badgeValue)
{
if (item == null) return;
if (UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
{
//change icon select
if (icon.EndsWith(".png"))
icon = icon.Replace(".png", "_selected.png");
else
icon += "_selected";
item.SelectedImage = UIImage.FromBundle(icon);
item.SelectedImage.AccessibilityIdentifier = icon;
item.SelectedImage = ScalingImageToSize(item.SelectedImage, new CGSize(20, 20)); // set the size here as you want
//change icon select
UITabBarAppearance app = new UITabBarAppearance();
app.ConfigureWithOpaqueBackground();
app.BackgroundColor = UIColor.Clear;
app.StackedLayoutAppearance.Normal.TitleTextAttributes = new UIStringAttributes() { Font = UIFont.FromName("Roboto Medium", 12), ForegroundColor = Color.FromHex("#808080").ToUIColor() };
app.StackedLayoutAppearance.Selected.TitleTextAttributes = new UIStringAttributes() { Font = UIFont.FromName("Roboto Medium", 13), ForegroundColor = Color.FromHex("#00AA13").ToUIColor() };
item.StandardAppearance = app;
if (UIDevice.CurrentDevice.CheckSystemVersion(15, 0))
{
item.ScrollEdgeAppearance = item.StandardAppearance;
}
}
}
public UIImage ScalingImageToSize(UIImage sourceImage, CGSize newSize)
{
if (UIScreen.MainScreen.Scale == 2.0) //#2x iPhone 6 7 8
{
UIGraphics.BeginImageContextWithOptions(newSize, false, 2.0f);
}
else if (UIScreen.MainScreen.Scale == 3.0) //#3x iPhone 6p 7p 8p...
{
UIGraphics.BeginImageContextWithOptions(newSize, false, 3.0f);
}
else
{
UIGraphics.BeginImageContext(newSize);
}
sourceImage.Draw(new CGRect(0, 0, newSize.Width, newSize.Height));
UIImage newImage = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return newImage;
}
}
public static class TabbarExtensions
{
readonly static int tabBarItemTag = 9999;
public static void addItemBadge(this UITabBar tabbar, int index)
{
if (tabbar.Items != null && tabbar.Items.Length == 0) return;
if (index >= tabbar.Items.Length) return;
removeItemBadge(tabbar, index);
var badgeView = new UIView();
badgeView.Tag = tabBarItemTag + index;
badgeView.Layer.CornerRadius = 3;
badgeView.BackgroundColor = UIColor.Red;
var tabFrame = tabbar.Frame;
var percentX = (index + 0.56) / tabbar.Items.Length;
var x = percentX * tabFrame.Width;
var y = tabFrame.Height * 0.1;
badgeView.Frame = new CoreGraphics.CGRect(x, y, 7, 7);
tabbar.AddSubview(badgeView);
}
public static bool removeItemBadge(this UITabBar tabbar, int index)
{
foreach (var subView in tabbar.Subviews)
{
if (subView.Tag == tabBarItemTag + index)
{
subView.RemoveFromSuperview();
return true;
}
}
return false;
}
}
}
Update
MainView.xaml
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
BackgroundColor="#fff"
xmlns:plugin="clr-namespace:Plugin.Badge.Abstractions;assembly=Plugin.Badge.Abstractions"
xmlns:views="clr-namespace:xxx.Views"
x:Class="xxx.Views.MainView">
<!--Pages can be added as references or inline-->
<views:Page1 Title="Page 1" IconImageSource="homeicon" BackgroundColor="#fff"/>
<views:Page2 Title="Page 2" IconImageSource="feeds" BackgroundColor="#fff"/>
<views:Page3 Title="Page 3" IconImageSource="moneys" BackgroundColor="#fbfbfb" />
<views:Page4 Title="Page 4" IconImageSource="chats" BackgroundColor="#fff" plugin:TabBadge.BadgeColor="Red"
plugin:TabBadge.BadgeText="1"/>
<views:Page5 Title="Page 5" IconImageSource="usericon" BackgroundColor="#fff"/>
</TabbedPage>
Enable Plugin.Badge I added this line in AssemblyInfo.cs: [assembly: ExportRenderer(typeof(TabbedPage), typeof(BadgedTabbedPageRenderer))] ----> Plugin.Badge it works, however everything I set in MyTabbedPageRenderer.cs not working
Everything works fine if I don't use: [assembly: ExportRenderer(typeof(TabbedPage), typeof(BadgedTabbedPageRenderer))]
---> And this is what I want:
Ask for help from everyone. Thanks

I have tested it on my side.The badge effect works well,below is the step that might give you some insights.
As you mentioned, it could be achieved by creating a custom renderer for TabbedPage:
Step1: Move what is in BadgedTabbedPageRenderer.cs here into MyTabbedPageRenderer.cs
Step2: Add [assembly: ExportRenderer(typeof(TabbedPage), typeof(MyTabbedPageRenderer))] in AssemblyInfo.cs and MyTabbedPageRenderer.cs.
Step3: Add two lines in the page you want to achieve the badge effect in the child page.
plugin:TabBadge.BadgeColor="Red"
plugin:TabBadge.BadgeText="1"

Related

How to add dot icon for TabbedPage strong Xamarin

I'm trying to add a red dot as shown below for TabbedPage. I have searched the internet but it doesn't look like anything. It looks like Badges but it's not. I want to permanently assign it at any time. I also don't know what keywords to search for exactly. For Xamarin iOS and Android.
MainView.xaml
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:views="clr-namespace:Appssss.Views"
x:Class="Appssss.Views.Mainview">
<!--Pages can be added as references or inline-->
<views:AboutPage Title="Page 1" IconImageSource="homeicon" BackgroundColor="#fff"/>
<views:LoginPage Title="Page 2" IconImageSource="feeds" BackgroundColor="#fff"/>
<views:AboutPage Title="Page 3" IconImageSource="moneys" BackgroundColor="#fbfbfb" />
<views:AboutPage Title="Page 4" IconImageSource="chats" BackgroundColor="#fff" />
<views:AboutPage Title="Page 5" IconImageSource="usericon" BackgroundColor="#fff"/>
</TabbedPage>
Ask for help from everyone. Thank you
Update
MyTabDotRenderer.cs
[assembly: ExportRenderer(typeof(TabbedPage), typeof(MyTabDotRenderer))]
namespace XXXX.iOS
{
internal class MyTabDotRenderer : TabbedRenderer
{
private bool _initialized;
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
//TabBar.ClipsToBounds = true;
TabBar.TintColor = UIColor.Gray;
TabBar.BarTintColor = UIColor.White;
TabBar.BackgroundColor = UIColor.White;
MessagingCenter.Subscribe<object, int>(this, "Add", (obj, index) => {
TabBar.addItemBadge(index);
});
MessagingCenter.Subscribe<object, int>(this, "Remove", (obj, index) => {
TabBar.removeItemBadge(index);
});
}
public override void ViewWillAppear(bool animated)
{
if (!_initialized)
{
if (TabBar?.Items == null)
return;
foreach (var item in TabBar.Items)
{
item.Image = ScalingImageToSize(item.Image, new CGSize(20, 20)); // set the size here as you want
}
var tabs = Element as TabbedPage;
if (tabs != null)
{
for (int i = 0; i < TabBar.Items.Length; i++)
{
UpdateItem(TabBar.Items[i], tabs.Children[i].Icon, tabs.Children[i].StyleId);
}
}
_initialized = true;
}
base.ViewWillAppear(animated);
}
private void UpdateItem(UITabBarItem item, string icon, string badgeValue)
{
if (item == null) return;
if (UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
{
//change icon select
if (icon.EndsWith(".png"))
icon = icon.Replace(".png", "_selected.png");
else
icon += "_selected";
item.SelectedImage = UIImage.FromBundle(icon);
item.SelectedImage.AccessibilityIdentifier = icon;
item.SelectedImage = ScalingImageToSize(item.SelectedImage, new CGSize(20, 20)); // set the size here as you want
//change icon select
UITabBarAppearance app = new UITabBarAppearance();
app.ConfigureWithOpaqueBackground();
app.BackgroundColor = UIColor.Clear;
app.StackedLayoutAppearance.Normal.TitleTextAttributes = new UIStringAttributes() { Font = UIFont.FromName("Roboto Medium", 12), ForegroundColor = Color.FromHex("#808080").ToUIColor() };
app.StackedLayoutAppearance.Selected.TitleTextAttributes = new UIStringAttributes() { Font = UIFont.FromName("Roboto Medium", 13), ForegroundColor = Color.FromHex("#00AA13").ToUIColor() };
item.StandardAppearance = app;
if (UIDevice.CurrentDevice.CheckSystemVersion(15, 0))
{
item.ScrollEdgeAppearance = item.StandardAppearance;
}
}
}
public UIImage ScalingImageToSize(UIImage sourceImage, CGSize newSize)
{
if (UIScreen.MainScreen.Scale == 2.0) //#2x iPhone 6 7 8
{
UIGraphics.BeginImageContextWithOptions(newSize, false, 2.0f);
}
else if (UIScreen.MainScreen.Scale == 3.0) //#3x iPhone 6p 7p 8p...
{
UIGraphics.BeginImageContextWithOptions(newSize, false, 3.0f);
}
else
{
UIGraphics.BeginImageContext(newSize);
}
sourceImage.Draw(new CGRect(0, 0, newSize.Width, newSize.Height));
UIImage newImage = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return newImage;
}
}
public static class TabbarExtensions
{
readonly static int tabBarItemTag = 9999;
public static void addItemBadge(this UITabBar tabbar, int index)
{
if (tabbar.Items != null && tabbar.Items.Length == 0) return;
if (index >= tabbar.Items.Length) return;
removeItemBadge(tabbar, index);
var badgeView = new UIView();
badgeView.Tag = tabBarItemTag + index;
badgeView.Layer.CornerRadius = 5;
badgeView.BackgroundColor = UIColor.Red;
var tabFrame = tabbar.Frame;
var percentX = (index + 0.56) / tabbar.Items.Length;
var x = percentX * tabFrame.Width;
var y = tabFrame.Height * 0.1;
badgeView.Frame = new CoreGraphics.CGRect(x, y, 10, 10);
tabbar.AddSubview(badgeView);
}
public static bool removeItemBadge(this UITabBar tabbar, int index)
{
foreach (var subView in tabbar.Subviews)
{
if (subView.Tag == tabBarItemTag + index)
{
subView.RemoveFromSuperview();
return true;
}
}
return false;
}
}
}
MainView.xaml.cs
public MainView(int index)
{
NavigationPage.SetHasNavigationBar(this, false);
InitializeComponent();
On<Xamarin.Forms.PlatformConfiguration.Android>().SetToolbarPlacement(ToolbarPlacement.Bottom);
SetPage(index);
MessagingCenter.Send<object, int>(this, "Add", 1);
}
void SetPage(int index)
{
CurrentPage = Children[index];
}
Update 2
CustomTabbedPageRenderer.cs in project Android
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Appssss.Droid.Renderers;
using Google.Android.Material.BottomNavigation;
using Google.Android.Material.Tabs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms.Platform.Android.AppCompat;
using static Google.Android.Material.BottomNavigation.BottomNavigationView;
using static Google.Android.Material.Tabs.TabLayout;
using View = Android.Views.View;
[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedPageRenderer))]
namespace Appssss.Droid.Renderers
{
public class CustomTabbedPageRenderer : TabbedPageRenderer
{
TabbedPage tabbedPage;
List<Android.Views.View> list = new List<Android.Views.View>();
public CustomTabbedPageRenderer(Context context) : base(context)
{
for (int i = 0; i < 5; i++)
{
Android.Views.View view = LayoutInflater.From(Context).Inflate(Resource.Drawable.MyView, null);
list.Add(view);
}
}
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Xamarin.Forms.TabbedPage> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || e.OldElement != null)
return;
if (e.NewElement != null)
{
MessagingCenter.Subscribe<object, int>(this, "Add", (obj, index) => {
TabLayout tablayout = (TabLayout)ViewGroup.GetChildAt(1);
ViewGroup vgroup = (ViewGroup)tablayout.GetChildAt(0);
for (int i = 0; i < vgroup.ChildCount; i++)
{
if (index == i)
{
var view = list[i];
if (view.Parent != null) break;
ViewGroup vvgroup = (ViewGroup)vgroup.GetChildAt(i);
MarginLayoutParams layout1 = new MarginLayoutParams(MarginLayoutParams.MatchParent, MarginLayoutParams.MatchParent);
layout1.SetMargins(120, -135, 0, 0);
vvgroup.AddView(view, layout1);
var ll = vvgroup.LayoutParameters as LinearLayout.LayoutParams;
ll.SetMargins(0, 30, 0, 0);
vvgroup.LayoutParameters = ll;
}
}
});
MessagingCenter.Subscribe<object, int>(this, "Remove", (obj, index) => {
TabLayout tablayout = (TabLayout)ViewGroup.GetChildAt(1);
ViewGroup vgroup = (ViewGroup)tablayout.GetChildAt(0);
for (int i = 0; i < vgroup.ChildCount; i++)
{
if (index == i)
{
var view = list[i];
if (view.Parent == null) break;
ViewGroup vvgroup = (ViewGroup)vgroup.GetChildAt(i);
vvgroup.RemoveView(view);
var ll = vvgroup.LayoutParameters as LinearLayout.LayoutParams;
ll.SetMargins(0, 0, 0, 0);
vvgroup.LayoutParameters = ll;
}
}
});
IEnumerable<View> children = GetAllChildViews(ViewGroup);
BottomNavigationView bottomNavBar = (BottomNavigationView)children.SingleOrDefault(view => view is BottomNavigationView);
if (bottomNavBar != null)
{
tabbedPage = e.NewElement;
bottomNavBar.NavigationItemSelected += BottomNavBar_NavigationItemSelected;
bottomNavBar.Menu.GetItem(0).SetIcon(Resource.Drawable.homeiconselect);
}
}
}
int previous;
private void BottomNavBar_NavigationItemSelected(object sender, BottomNavigationView.NavigationItemSelectedEventArgs e)
{
var current = e.Item.ItemId;
switch (e.Item.ToString())
{
case "Page 1":
e.Item.SetIcon(Resource.Drawable.homeiconselect);
break;
case "Page 2":
e.Item.SetIcon(Resource.Drawable.feedsselect);
break;
case "Page 3":
e.Item.SetIcon(Resource.Drawable.moneysselect);
break;
case "Page 4":
e.Item.SetIcon(Resource.Drawable.chatsselect);
break;
case "Page 5":
e.Item.SetIcon(Resource.Drawable.usericonselect);
break;
default:
break;
}
var previousView = sender as BottomNavigationView;
IMenu menu = previousView.Menu;
var previousItem = menu.GetItem(previous);
if (previous != current)
{
if (previousItem.IsChecked)
{
switch (previousItem.ToString())
{
case "Page 1":
previousItem.SetIcon(Resource.Drawable.homeicon);
break;
case "Page 2":
previousItem.SetIcon(Resource.Drawable.feeds);
break;
case "Page 3":
previousItem.SetIcon(Resource.Drawable.moneys);
break;
case "Page 4":
previousItem.SetIcon(Resource.Drawable.chats);
break;
case "Page 5":
previousItem.SetIcon(Resource.Drawable.usericon);
break;
default:
break;
}
}
}
tabbedPage.CurrentPage = tabbedPage.Children[current];
previous = current;
}
private IEnumerable<View> GetAllChildViews(View view)
{
if (!(view is ViewGroup group))
return new List<View> { view };
List<View> result = new List<View>();
int childCount = group.ChildCount;
for (int i = 0; i < childCount; i++)
{
View child = group.GetChildAt(i);
List<View> childList = new List<View> { child };
childList.AddRange(GetAllChildViews(child));
result.AddRange(childList);
}
return result.Distinct();
}
}
}
MainView.xaml.cs
public Mainview(int index)
{
InitializeComponent();
On<Xamarin.Forms.PlatformConfiguration.Android>().SetToolbarPlacement(ToolbarPlacement.Bottom);
SetPage(index);
}
void SetPage(int index)
{
CurrentPage = Children[index];
}
protected override void OnAppearing()
{
base.OnAppearing();
MessagingCenter.Send<object, int>(this, "Add", 1);
///On iOS this works fine
}
Add a xml in drawable folder called MyView.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:background="#drawable/Reddot"
android:layout_width="12dp"
android:layout_height="12dp"/>
</LinearLayout>
Add a xml in drawable folder called Reddot.xml
<?xml version="1.0" encoding="utf-8" ?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#ff0000"/>
<size android:width="12dp" android:height="12dp"/>
</shape>
It could be achieved by creating a custom renderer for TabbedPage .
iOS Solution
[assembly: ExportRenderer(typeof(TabbedPage), typeof(MyTabRenderer))]
namespace MyForms.iOS
{
internal class MyTabRenderer : TabbedRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
MessagingCenter.Subscribe<object, int>(this, "Add", (obj,index) => {
TabBar.addItemBadge(index);
});
MessagingCenter.Subscribe<object, int>(this, "Remove", (obj,index) => {
TabBar.removeItemBadge(index);
});
}
}
public static class TabbarExtensions
{
readonly static int tabBarItemTag = 9999;
public static void addItemBadge(this UITabBar tabbar, int index)
{
if (tabbar.Items != null && tabbar.Items.Length == 0) return;
if (index >= tabbar.Items.Length) return;
removeItemBadge(tabbar, index);
var badgeView = new UIView();
badgeView.Tag = tabBarItemTag + index;
badgeView.Layer.CornerRadius = 5;
badgeView.BackgroundColor = UIColor.Red;
var tabFrame = tabbar.Frame;
var percentX = (index + 0.56) / tabbar.Items.Length;
var x = percentX * tabFrame.Width;
var y = tabFrame.Height * 0.1;
badgeView.Frame = new CoreGraphics.CGRect(x, y, 10, 10);
tabbar.AddSubview(badgeView);
}
public static bool removeItemBadge(this UITabBar tabbar, int index)
{
foreach(var subView in tabbar.Subviews)
{
if(subView.Tag == tabBarItemTag + index)
{
subView.RemoveFromSuperview();
return true;
}
}
return false;
}
}
}
Android Solution
Add a xml in drawable folder called Reddot.xml .
<?xml version="1.0" encoding="utf-8" ?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#ff0000"/>
<size android:width="12dp" android:height="12dp"/>
</shape>
Add a xml in drawable folder called MyView.xml .
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:background="#drawable/Reddot"
android:layout_width="12dp"
android:layout_height="12dp"/>
</LinearLayout>
3.custom renderer
[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedPageRenderer))]
namespace MyForms.Droid
{
public class CustomTabbedPageRenderer : TabbedPageRenderer
{
List<Android.Views.View> list = new List<Android.Views.View>();
public CustomTabbedPageRenderer(Context context) : base(context)
{
for (int i = 0; i < 3;i++)
{
Android.Views.View view = LayoutInflater.From(Context).Inflate(Resource.Drawable.MyView, null);
list.Add(view);
}
}
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Xamarin.Forms.TabbedPage> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || e.OldElement != null)
return;
MessagingCenter.Subscribe<object, int>(this, "Add", (obj, index) => {
TabLayout tablayout = (TabLayout)ViewGroup.GetChildAt(1);
ViewGroup vgroup = (ViewGroup)tablayout.GetChildAt(0);
for (int i = 0; i < vgroup.ChildCount; i++)
{
if(index == i)
{
var view = list[i];
if (view.Parent != null) break;
ViewGroup vvgroup = (ViewGroup)vgroup.GetChildAt(i);
MarginLayoutParams layout1 = new MarginLayoutParams(MarginLayoutParams.MatchParent, MarginLayoutParams.MatchParent);
layout1.SetMargins(120, -135, 0, 0);
vvgroup.AddView(view,layout1);
var ll = vvgroup.LayoutParameters as LinearLayout.LayoutParams;
ll.SetMargins(0, 30, 0,0 );
vvgroup.LayoutParameters = ll;
}
}
});
MessagingCenter.Subscribe<object, int>(this, "Remove", (obj, index) => {
TabLayout tablayout = (TabLayout)ViewGroup.GetChildAt(1);
ViewGroup vgroup = (ViewGroup)tablayout.GetChildAt(0);
for (int i = 0; i < vgroup.ChildCount; i++)
{
if(index == i)
{
var view = list[i];
if (view.Parent == null) break;
ViewGroup vvgroup = (ViewGroup)vgroup.GetChildAt(i);
vvgroup.RemoveView(view);
var ll = vvgroup.LayoutParameters as LinearLayout.LayoutParams;
ll.SetMargins(0, 0, 0, 0);
vvgroup.LayoutParameters = ll;
}
}
});
}
}
}
Usage in Forms project
//add red dot
MessagingCenter.Send<object, int>(this, "Add", 1);
//remove red dot
MessagingCenter.Send<object, int>(this, "Remove", 1);
Screen shot

Remove border top, change font family,.... not working in Xamarin iOS latest version

In my Xamarin iOS app I am trying to hide the top border and change the font family of TabbedPage. For Xamarin version 5.0.0.2021 it works fine. However when I update Xamarin version to 5.0.0.2244 it doesn't seem to work? There must be something unusual here. This is how I changed the font Family and Border Top TabbedPage:
MyTabbedPageRenderer
Change icon size, fontsize, font family,... TabbedPage
[assembly: ExportRenderer(typeof(TabbedPage), typeof(MyTabbedPageRenderer))]
....
public class MyTabbedPageRenderer : TabbedRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
TabBar.TintColor = UIColor.Gray;
TabBar.BarTintColor = UIColor.White;
TabBar.BackgroundColor = UIColor.White;
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if (TabBar?.Items == null)
return;
foreach (var item in TabBar.Items)
{
item.Image = ScalingImageToSize(item.Image, new CGSize(30, 30)); // set the size here as you want
}
var tabs = Element as TabbedPage;
if (tabs != null)
{
for (int i = 0; i < TabBar.Items.Length; i++)
{
UpdateTabBarItem(TabBar.Items[i]);
}
}
}
private void UpdateTabBarItem(UITabBarItem item)
{
if (item == null)
return;
// Set the font for the title.
item.SetTitleTextAttributes(new UITextAttributes() { Font = UIFont.FromName("Quicksand Regular", 12), TextColor = Color.FromHex("#808080").ToUIColor() }, UIControlState.Normal);
item.SetTitleTextAttributes(new UITextAttributes() { Font = UIFont.FromName("Quicksand Regular", 13), TextColor = Color.FromHex("#00AA13").ToUIColor() }, UIControlState.Selected);
}
...
}
Remove border top TabbedPage
AppDelegate
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
UITabBar.Appearance.BackgroundImage = new UIImage();
UITabBar.Appearance.ShadowImage = new UIImage();
UITabBar.Appearance.SelectedImageTintColor = UIColor.FromRGB(0, 170, 19);
LoadApplication(new App());
}
I tried rebuilding the project, restarting everything, but it doesn't seem to work on Xamarin version 5.0.0.2244. Ask for help. Thanks.
There are some api changes in iOS 15, so we need to change the way .
Remove border top
TabBar.ClipsToBounds = true;
Change the font and text color on items
private void UpdateTabBarItem(UITabBarItem item)
{
if (item == null) return;
if(UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
{
UITabBarAppearance app = new UITabBarAppearance();
app.ConfigureWithOpaqueBackground();
app.BackgroundColor = UIColor.Clear;
app.StackedLayoutAppearance.Normal.TitleTextAttributes = new UIStringAttributes() { Font = UIFont.FromName("GillSans-UltraBold", 12), ForegroundColor = Color.FromHex("#00FF00").ToUIColor() };
app.StackedLayoutAppearance.Selected.TitleTextAttributes = new UIStringAttributes() { Font = UIFont.FromName("GillSans-UltraBold", 20), ForegroundColor = Color.FromHex("#FF0000").ToUIColor() };
item.StandardAppearance = app;
if (UIDevice.CurrentDevice.CheckSystemVersion(15, 0))
{
item.ScrollEdgeAppearance = item.StandardAppearance;
}
}
}
Tested on my side , works fine .
Refer to
https://stackoverflow.com/a/69361301/8187800.

How can I add a location button to xamarin forms maps

I implemented XF Maps, and added permissions from XF.Essentials
On ANdroid is verything fine, but on iOS I cant see location button, after I clicked approve for permissions?
What else I need to add in order to see button for location (user geolocation)?
...
private async void GetPermissions()
{
var status = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();
if (status != PermissionStatus.Granted)
{
status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
}
if (status != PermissionStatus.Granted)
{
await Shell.Current.DisplayAlert("Permission Denied", "We Need to access your Location. But it is not granted", "OK");
}
}
...
my iOS renderer
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace OperaMobile.iOS.Renderers
{
public class CustomMapRenderer : MapRenderer
{
UIStackView customPinView;
ObservableCollection<CustomPin> customPins;
protected override void OnElementChanged(ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
var nativeMap = Control as MKMapView;
nativeMap.GetViewForAnnotation = null;
nativeMap.CalloutAccessoryControlTapped -= OnCalloutAccessoryControlTapped;
nativeMap.DidSelectAnnotationView -= OnDidSelectAnnotationView;
nativeMap.DidDeselectAnnotationView -= OnDidDeselectAnnotationView;
}
if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
var nativeMap = Control as MKMapView;
customPins = formsMap.CustomPins;
nativeMap.GetViewForAnnotation = GetViewForAnnotation;
nativeMap.CalloutAccessoryControlTapped += OnCalloutAccessoryControlTapped;
nativeMap.DidSelectAnnotationView += OnDidSelectAnnotationView;
nativeMap.DidDeselectAnnotationView += OnDidDeselectAnnotationView;
}
}
protected override MKAnnotationView GetViewForAnnotation(MKMapView mapView, IMKAnnotation annotation)
{
MKAnnotationView annotationView = null;
if (annotation is MKUserLocation)
return null;
var customPin = GetCustomPin(annotation as MKPointAnnotation);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
annotationView = mapView.DequeueReusableAnnotation(customPin.Label);
if (annotationView == null)
{
annotationView = new CustomMKAnnotationView(annotation, customPin.Label);
annotationView.Image = UIImage.FromFile("pin.png");
annotationView.CalloutOffset = new CGPoint(0, 0);
UIImageView uIImageView = new UIImageView(UIImage.FromFile("monkey.png"));
uIImageView.Frame = new CGRect(0, 0, 75, 100);
annotationView.LeftCalloutAccessoryView = uIImageView;
//annotationView.LeftCalloutAccessoryView = new UIImageView(UIImage.FromFile("monkey.png"));
//annotationView.RightCalloutAccessoryView = UIButton.FromType(UIButtonType.DetailDisclosure);
((CustomMKAnnotationView)annotationView).Name = customPin.Label;
//((CustomMKAnnotationView)annotationView).Url = customPin.Url;
customPinView = new UIStackView();
foreach (var item in customPin.InfoBox.DetailsObjectInfos)
{
var label = new UILabel();
label.Text = item.BoldLabelTitle + item.LabelValue;
label.BackgroundColor = UIColor.White;
label.Font.WithSize(36);
customPinView.AddArrangedSubview(label);
}
customPinView.Frame = new CGRect(0, 0, 300, 84);
customPinView.Axis = UILayoutConstraintAxis.Vertical;
customPinView.Distribution = UIStackViewDistribution.EqualSpacing;
customPinView.Spacing = 1;
customPinView.Alignment = UIStackViewAlignment.Fill;
annotationView.DetailCalloutAccessoryView = customPinView;
UITapGestureRecognizer tapGestureRecognizer = new
UITapGestureRecognizer((gesture) =>
{
Shell.Current.GoToAsync(Routes.ObjectParametersPage);
});
annotationView.DetailCalloutAccessoryView.AddGestureRecognizer(tapGestureRecognizer);
}
annotationView.CanShowCallout = true;
return annotationView;
}
protected virtual void OnCalloutAccessoryControlTapped(object sender, MKMapViewAccessoryTappedEventArgs e)
{
Shell.Current.GoToAsync(Routes.ObjectParametersPage);
//(App.Current as App).NavigationPage.Navigation.PushAsync(new ContentPage());
}
void OnDidSelectAnnotationView(object sender, MKAnnotationViewEventArgs e)
{
CustomMKAnnotationView customView = e.View as CustomMKAnnotationView;
}
void OnDidDeselectAnnotationView(object sender, MKAnnotationViewEventArgs e)
{
if (!e.View.Selected)
{
customPinView.RemoveFromSuperview();
customPinView.Dispose();
customPinView = null;
}
}
CustomPin GetCustomPin(MKPointAnnotation annotation)
{
var position = new Position(annotation.Coordinate.Latitude, annotation.Coordinate.Longitude);
foreach (var pin in customPins)
{
if (pin.Position == position)
{
return pin;
}
}
return null;
}
}}
Not sure how to add the location button in your project, maybe you can add a customed ImageButton with location icon in Xaml .
Xaml code:
<RelativeLayout>
<local:CustomMap x:Name="customMap"
IsShowingUser="True"
MapType="Street"
IsVisible="true"
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent,
Property=Width,
Factor=0,
Constant=0}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent,
Property=Height,
Factor=0,
Constant=0}"
RelativeLayout.WidthConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=1,Constant=0}"
RelativeLayout.HeightConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=1,Constant=0}" />
<ImageButton Source="location.png"
Clicked="Button_Clicked"
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent,
Property=Width,
Factor=0.6,
Constant=100}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent,
Property=Height,
Factor=0.8,
Constant=80}" />
</RelativeLayout>
The contentpage.cs :
private async void Button_Clicked(object sender, System.EventArgs e)
{
var request = new GeolocationRequest(GeolocationAccuracy.Medium);
var location = await Geolocation.GetLocationAsync(request);
if (location != null)
{
Console.WriteLine($"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude: {location.Altitude}");
}
customMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(location.Latitude, -location.Longitude), Distance.FromMiles(1.0)));
}
The effect:
==================================Update #1=================================
In xamarin forms , you can hide the xaml location button only in Android , then iOS will show the xaml location button.
protected override void OnAppearing()
{
base.OnAppearing();
if(Device.RuntimePlatform == "Android")
{
LocationButton.IsVisible = false;
}
}
The LocationButton is defined in Xaml : <ImageButton x:Name="LocationButton" ... />
==================================Update #2=================================
If need to test location in ios simulator, you need to choose the location manually. Clicking settings of simulatoras follow:
Choose a location:

Capturing a hand-drawn signature with Xamarin Forms (PCL)

I am trying to figure out how I can ask the user for a signature area so they can sign with their finger then save that signature to a file and I am reaching a dead end. I have looked at Kimserey's blog, the CrossGraphics library and SkiaSharp but these all seem to be geared around making the image through code vs a user drawing with their finger. The solution needs to be used in a pcl project and will be deployed to Android, iOS, and UWP. Does anyone have an idea?
As #Jason commented, use SignaturePad.
The NuGet can be found here: https://www.nuget.org/packages/Xamarin.Controls.SignaturePad.Forms
It works on basically any platform.
Source: https://github.com/xamarin/SignaturePad
An example page might be this:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:signature="clr-namespace:SignaturePad.Forms;assembly=SignaturePad.Forms"
x:Class="Samples.Views.SignatureXamlView">
<Grid>
<signature:SignaturePadView />
</Grid>
</ContentPage>
SignaturePadDemoPage.xaml
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
BackgroundColor="Gray"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-
namespace:SignaturePadDemo;assembly:SignaturePadDemo"
x:Class="SignaturePadDemo.SignaturePadDemoPage">
<Grid HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Padding="30">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Text="SignaturePad Demo" Grid.Row="0" VerticalOptions="Start" HorizontalOptions="Center" TextColor="White" FontSize="25"/>
<local:ImageWithTouch x:Name="imgSiganturePad" Grid.Row="1" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" BackgroundColor="White" CurrentLineColor="Fuchsia"/>
<Grid Grid.Row="2" VerticalOptions="EndAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Button x:Name="btnSave" Text="Save the Image" Grid.Row="0" HorizontalOptions="FillAndExpand" TextColor="Blue" BackgroundColor="White" Clicked="btnSaveImage_Click"/>
<Button x:Name="btnSee" Text="See the Image" Grid.Row="1" HorizontalOptions="FillAndExpand" TextColor="Blue" BackgroundColor="White" Clicked="btnSeeImage_Click"/>
<Button x:Name="btnClear" Text="Clear" Grid.Row="2" HorizontalOptions="FillAndExpand" TextColor="Blue" BackgroundColor="White" Clicked="btnClear_Click"/>
</Grid>
</Grid>
SignaturePadDemoPage.xaml.cs
using System;
using Xamarin.Forms;
namespace SignaturePadDemo
{
public partial class SignaturePadDemoPage : ContentPage
{
public SignaturePadDemoPage()
{
InitializeComponent();
}
private void btnSaveImage_Click(object sender, EventArgs e)
{
var imgPath = DependencyService.Get<ISign>().Sign();
imgSiganturePad.SavedImagePath = imgPath;
btnSee.IsEnabled = true;
DisplayAlert("SignaturePadDemo", "Your siganture saved succesfully", "Ok");
}
private void btnSeeImage_Click(object sender, EventArgs e)
{
Navigation.PushModalAsync(new
SignatureDetailPage(imgSiganturePad.SavedImagePath));
imgSiganturePad.ClearPath = !imgSiganturePad.ClearPath;
}
private void btnClear_Click(object sender, EventArgs e)
{
imgSiganturePad.ClearPath = !imgSiganturePad.ClearPath;
DisplayAlert("SignaturePadDemo", "Siganture was clear", "Ok");
}
}
}
CustomRenderer for Image
PCL:
ImageWithTouch.cs
using System;
using Xamarin.Forms;
namespace SignaturePadDemo
{
public class ImageWithTouch : Image
{
public static readonly BindableProperty CurrentLineColorProperty =
BindableProperty.Create((ImageWithTouch w) => w.CurrentLineColor, Color.Default);
public static readonly BindableProperty CurrentLineWidthProperty =
BindableProperty.Create((ImageWithTouch w) => w.CurrentLineWidth, 1);
public static readonly BindableProperty CurrentImageProperty =
BindableProperty.Create((ImageWithTouch w) => w.CurrentImagePath, "");
public static readonly BindableProperty ClearImagePathProperty =
BindableProperty.Create((ImageWithTouch w) => w.ClearPath, false);
public static readonly BindableProperty SavedImagePathProperty =
BindableProperty.Create((ImageWithTouch w) => w.SavedImagePath, "");
public Color CurrentLineColor
{
get
{
return (Color)GetValue(CurrentLineColorProperty);
}
set
{
SetValue(CurrentLineColorProperty, value);
}
}
public int CurrentLineWidth
{
get
{
return (int)GetValue(CurrentLineWidthProperty);
}
set
{
SetValue(CurrentLineWidthProperty, value);
}
}
public string CurrentImagePath
{
get
{
return (string)GetValue(CurrentImageProperty);
}
set
{
SetValue(CurrentImageProperty, value);
}
}
public bool ClearPath
{
get
{
return (bool)GetValue(ClearImagePathProperty);
}
set
{
SetValue(ClearImagePathProperty, value);
}
}
public string SavedImagePath
{
get
{
return (string)GetValue(SavedImagePathProperty);
}
set
{
SetValue(SavedImagePathProperty, value);
}
}
}
}
Xamarin.Android:
ImageWithTouchRenderer.cs
using System;
using System.ComponentModel;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms;
using Android.Graphics;
using Java.IO;
using System.IO;
using SignaturePadDemo;
using SignaturePadDemo.Droid;
[assembly: ExportRenderer(typeof(ImageWithTouch),
typeof(ImageWithTouchRenderer))]
namespace SignaturePadDemo.Droid
{
public class ImageWithTouchRenderer : ViewRenderer<ImageWithTouch, DrawView>
{
protected override void OnElementChanged(ElementChangedEventArgs<ImageWithTouch> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
SetNativeControl(new DrawView(Context));
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == ImageWithTouch.ClearImagePathProperty.PropertyName)
{
Control.Clear();
}
else if (e.PropertyName == ImageWithTouch.SavedImagePathProperty.PropertyName)
{
Bitmap curDrawingImage = Control.GetImageFromView();
Byte[] imgBytes = ImageHelper.BitmapToBytes(curDrawingImage);
Java.IO.File f = new Java.IO.File(Element.SavedImagePath);
f.CreateNewFile();
FileOutputStream fo = new FileOutputStream(f);
fo.Write(imgBytes);
fo.Close();
}
else
{
UpdateControl(true);
}
}
private void UpdateControl(bool bDisplayFlag)
{
Control.CurrentLineColor = Element.CurrentLineColor.ToAndroid();
Control.PenWidth = Element.CurrentLineWidth * 3;
Control.ImageFilePath = Element.CurrentImagePath;
if (bDisplayFlag)
{
Control.LoadImageFromFile();
Control.Invalidate();
}
}
}
}
Xamarin.iOS:
ImageWithTouchRenderer.cs
using System.Drawing;
using Xamarin.Forms.Platform.iOS;
using Xamarin.Forms;
using System.ComponentModel;
using UIKit;
using Foundation;
using SignaturePadDemo;
using SignaturePadDemo.iOS;
[assembly: ExportRenderer(typeof(ImageWithTouch), typeof(ImageWithTouchRenderer))]
namespace SignaturePadDemo.iOS
{
public class ImageWithTouchRenderer : ViewRenderer<ImageWithTouch, DrawView>
{
protected override void OnElementChanged(ElementChangedEventArgs<ImageWithTouch> e)
{
base.OnElementChanged(e);
SetNativeControl(new DrawView(RectangleF.Empty));
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == ImageWithTouch.ClearImagePathProperty.PropertyName)
{
Control.Clear();
}
else if (e.PropertyName == ImageWithTouch.SavedImagePathProperty.PropertyName)
{
UIImage curDrawingImage = Control.GetImageFromView();
NSData data = curDrawingImage.AsJPEG();
NSError error = new NSError();
bool bSuccess = data.Save(Element.SavedImagePath, true, out error);
}
else
{
UpdateControl(e.PropertyName == ImageWithTouch.CurrentLineColorProperty.PropertyName ||
e.PropertyName == ImageWithTouch.CurrentImageProperty.PropertyName ||
e.PropertyName == ImageWithTouch.CurrentLineWidthProperty.PropertyName);
}
}
private void UpdateControl(bool bDisplayFlag)
{
Control.CurrentLineColor = Element.CurrentLineColor.ToUIColor();
Control.PenWidth = Element.CurrentLineWidth;
if (Control.ImageFilePath != Element.CurrentImagePath)
{
Control.ImageFilePath = Element.CurrentImagePath;
Control.LoadImageFromFile();
}
if (bDisplayFlag)
{
Control.SetNeedsDisplay();
}
}
}
}
DependencyService to get path to store Image in Local storage
ISign.cs
using System;
namespace SignaturePadDemo
{
public interface ISign
{
string Sign();
}
}
Xamarin.Android:
ISignService.cs
using System;
using SignaturePadDemo.Droid;
using Xamarin.Forms;
[assembly: Dependency(typeof(ISignService))]
namespace SignaturePadDemo.Droid
{
public class ISignService : ISign
{
public string Sign()
{
string savedFileName = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "/temp_" + DateTime.Now.ToString("yyyy_mm_dd_hh_mm_ss") + ".jpg";
return savedFileName;
}
}
}
DrawView.cs
using Android.Views;
using Android.Graphics;
using Android.Content;
using System;
namespace SignaturePadDemo.Droid
{
public class DrawView : View
{
public DrawView(Context context)
: base(context)
{
Start();
}
public Color CurrentLineColor { get; set; }
public String ImageFilePath { get; set; }
public float PenWidth { get; set; }
private Path DrawPath;
private Paint DrawPaint;
private Paint CanvasPaint;
private Canvas DrawCanvas;
private Bitmap CanvasBitmap;
private int w, h;
private Bitmap _image;
private void Start()
{
CurrentLineColor = Color.Black;
PenWidth = 5.0f;
DrawPath = new Path();
DrawPaint = new Paint
{
Color = CurrentLineColor,
AntiAlias = true,
StrokeWidth = PenWidth
};
DrawPaint.SetStyle(Paint.Style.Stroke);
DrawPaint.StrokeJoin = Paint.Join.Round;
DrawPaint.StrokeCap = Paint.Cap.Round;
CanvasPaint = new Paint
{
Dither = true
};
}
public void Clear()
{
try
{
DrawPath = new Path();
CanvasBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
DrawCanvas = new Canvas(CanvasBitmap);
DrawCanvas.DrawColor(Color.White, PorterDuff.Mode.Multiply);
CanvasBitmap.EraseColor(Color.Transparent);
DrawPaint = new Paint
{
Color = CurrentLineColor,
AntiAlias = true,
StrokeWidth = PenWidth
};
DrawPaint.SetStyle(Paint.Style.Stroke);
DrawPaint.StrokeJoin = Paint.Join.Round;
DrawPaint.StrokeCap = Paint.Cap.Round;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Invalidate();
}
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
base.OnSizeChanged(w, h, oldw, oldh);
if (w > 0 && h > 0)
{
try
{
CanvasBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
DrawCanvas = new Canvas(CanvasBitmap);
this.w = w;
this.h = h;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
DrawPaint.Color = CurrentLineColor;
DrawPaint.StrokeWidth = PenWidth;
canvas.DrawBitmap(CanvasBitmap, 0, 0, CanvasPaint);
canvas.DrawPath(DrawPath, DrawPaint);
}
public override bool OnTouchEvent(MotionEvent e)
{
var touchX = e.GetX();
var touchY = e.GetY();
switch (e.Action)
{
case MotionEventActions.Down:
DrawPath.MoveTo(touchX, touchY);
break;
case MotionEventActions.Move:
DrawPath.LineTo(touchX, touchY);
break;
case MotionEventActions.Up:
DrawCanvas.DrawPath(DrawPath, DrawPaint);
DrawPath.Reset();
break;
default:
return false;
}
Invalidate();
return true;
}
public void LoadImageFromFile()
{
if (ImageFilePath != null && ImageFilePath != "")
{
_image = BitmapFactory.DecodeFile(ImageFilePath);
}
}
public Bitmap GetImageFromView()
{
Bitmap tempBitmap = null;
try
{
tempBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
DrawCanvas = new Canvas(tempBitmap);
if (_image != null)
{
DrawPaint.SetStyle(Paint.Style.Fill);
DrawPaint.Color = Color.White;
DrawCanvas.DrawRect(new Rect(0, 0, w, h), DrawPaint);
float scaleX = (float)_image.Width / w;
float scaleY = (float)_image.Height / h;
Rect outRect = new Rect();
int outWidth, outHeight;
if (scaleX > scaleY)
{
outWidth = w;
outHeight = (int)(_image.Height / scaleX);
}
else
{
outWidth = (int)(_image.Width / scaleY);
outHeight = h;
}
outRect.Left = w / 2 - outWidth / 2;
outRect.Top = h / 2 - outHeight / 2;
outRect.Right = w / 2 + outWidth / 2;
outRect.Bottom = h / 2 + outHeight / 2;
DrawCanvas.DrawBitmap(_image, new Rect(0, 0, _image.Width, _image.Height), outRect, DrawPaint);
}
else
{
DrawPaint.SetStyle(Paint.Style.Fill);
DrawPaint.Color = Color.White;
DrawCanvas.DrawRect(new Rect(0, 0, w, h), DrawPaint);
}
DrawPaint.Color = CurrentLineColor;
DrawCanvas.DrawBitmap(CanvasBitmap, 0, 0, CanvasPaint);
DrawCanvas.DrawPath(DrawPath, DrawPaint);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return tempBitmap;
}
}
}
ImageHelper.cs
using Android.Views;
using Android.Graphics;
using Android.Content;
using System;
namespace SignaturePadDemo.Droid
{
public class DrawView : View
{
public DrawView(Context context)
: base(context)
{
Start();
}
public Color CurrentLineColor { get; set; }
public String ImageFilePath { get; set; }
public float PenWidth { get; set; }
private Path DrawPath;
private Paint DrawPaint;
private Paint CanvasPaint;
private Canvas DrawCanvas;
private Bitmap CanvasBitmap;
private int w, h;
private Bitmap _image;
private void Start()
{
CurrentLineColor = Color.Black;
PenWidth = 5.0f;
DrawPath = new Path();
DrawPaint = new Paint
{
Color = CurrentLineColor,
AntiAlias = true,
StrokeWidth = PenWidth
};
DrawPaint.SetStyle(Paint.Style.Stroke);
DrawPaint.StrokeJoin = Paint.Join.Round;
DrawPaint.StrokeCap = Paint.Cap.Round;
CanvasPaint = new Paint
{
Dither = true
};
}
public void Clear()
{
try
{
DrawPath = new Path();
CanvasBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
DrawCanvas = new Canvas(CanvasBitmap);
DrawCanvas.DrawColor(Color.White, PorterDuff.Mode.Multiply);
CanvasBitmap.EraseColor(Color.Transparent);
DrawPaint = new Paint
{
Color = CurrentLineColor,
AntiAlias = true,
StrokeWidth = PenWidth
};
DrawPaint.SetStyle(Paint.Style.Stroke);
DrawPaint.StrokeJoin = Paint.Join.Round;
DrawPaint.StrokeCap = Paint.Cap.Round;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Invalidate();
}
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
base.OnSizeChanged(w, h, oldw, oldh);
if (w > 0 && h > 0)
{
try
{
CanvasBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
DrawCanvas = new Canvas(CanvasBitmap);
this.w = w;
this.h = h;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
DrawPaint.Color = CurrentLineColor;
DrawPaint.StrokeWidth = PenWidth;
canvas.DrawBitmap(CanvasBitmap, 0, 0, CanvasPaint);
canvas.DrawPath(DrawPath, DrawPaint);
}
public override bool OnTouchEvent(MotionEvent e)
{
var touchX = e.GetX();
var touchY = e.GetY();
switch (e.Action)
{
case MotionEventActions.Down:
DrawPath.MoveTo(touchX, touchY);
break;
case MotionEventActions.Move:
DrawPath.LineTo(touchX, touchY);
break;
case MotionEventActions.Up:
DrawCanvas.DrawPath(DrawPath, DrawPaint);
DrawPath.Reset();
break;
default:
return false;
}
Invalidate();
return true;
}
public void LoadImageFromFile()
{
if (ImageFilePath != null && ImageFilePath != "")
{
_image = BitmapFactory.DecodeFile(ImageFilePath);
}
}
public Bitmap GetImageFromView()
{
Bitmap tempBitmap = null;
try
{
tempBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
DrawCanvas = new Canvas(tempBitmap);
if (_image != null)
{
DrawPaint.SetStyle(Paint.Style.Fill);
DrawPaint.Color = Color.White;
DrawCanvas.DrawRect(new Rect(0, 0, w, h), DrawPaint);
float scaleX = (float)_image.Width / w;
float scaleY = (float)_image.Height / h;
Rect outRect = new Rect();
int outWidth, outHeight;
if (scaleX > scaleY)
{
outWidth = w;
outHeight = (int)(_image.Height / scaleX);
}
else
{
outWidth = (int)(_image.Width / scaleY);
outHeight = h;
}
outRect.Left = w / 2 - outWidth / 2;
outRect.Top = h / 2 - outHeight / 2;
outRect.Right = w / 2 + outWidth / 2;
outRect.Bottom = h / 2 + outHeight / 2;
DrawCanvas.DrawBitmap(_image, new Rect(0, 0, _image.Width, _image.Height), outRect, DrawPaint);
}
else
{
DrawPaint.SetStyle(Paint.Style.Fill);
DrawPaint.Color = Color.White;
DrawCanvas.DrawRect(new Rect(0, 0, w, h), DrawPaint);
}
DrawPaint.Color = CurrentLineColor;
DrawCanvas.DrawBitmap(CanvasBitmap, 0, 0, CanvasPaint);
DrawCanvas.DrawPath(DrawPath, DrawPaint);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return tempBitmap;
}
}
}
Xamarin.iOS:
ISignService.cs
using System;
using SignaturePadDemo.iOS;
using Xamarin.Forms;
[assembly: Dependency(typeof(ISignService))]
namespace SignaturePadDemo.iOS
{
public class ISignService : ISign
{
public string Sign()
{
string savedFileName = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "/temp_" + DateTime.Now.ToString("yyyy_mm_dd_hh_mm_ss") + ".jpg";
return savedFileName;
}
}
}
DrawView.cs
using System;
using UIKit;
using System.Drawing;
using CoreGraphics;
using System.Collections.Generic;
using Foundation;
namespace SignaturePadDemo.iOS
{
public class DrawView : UIView
{
public DrawView(RectangleF frame) : base(frame)
{
DrawPath = new CGPath();
CurrentLineColor = UIColor.Black;
PenWidth = 3.0f;
Lines = new List<VESLine>();
}
private PointF PreviousPoint;
private CGPath DrawPath;
private byte IndexCount;
private UIBezierPath CurrentPath;
private List<VESLine> Lines;
public UIColor CurrentLineColor { get; set; }
public String ImageFilePath { get; set; }
public float PenWidth { get; set; }
private UIImage _image = null;
public void Clear()
{
Lines.Clear();
DrawPath.Dispose();
DrawPath = new CGPath();
SetNeedsDisplay();
}
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
IndexCount++;
var path = new UIBezierPath
{
LineWidth = PenWidth
};
var touch = (UITouch)touches.AnyObject;
PreviousPoint = (PointF)touch.PreviousLocationInView(this);
var newPoint = touch.LocationInView(this);
path.MoveTo(newPoint);
InvokeOnMainThread(SetNeedsDisplay);
CurrentPath = path;
var line = new VESLine
{
Path = CurrentPath,
Color = CurrentLineColor,
Index = IndexCount
};
Lines.Add(line);
}
public override void TouchesMoved(NSSet touches, UIEvent evt)
{
var touch = (UITouch)touches.AnyObject;
var currentPoint = touch.LocationInView(this);
if (Math.Abs(currentPoint.X - PreviousPoint.X) >= 4 ||
Math.Abs(currentPoint.Y - PreviousPoint.Y) >= 4)
{
var newPoint = new PointF((float)(currentPoint.X + PreviousPoint.X) / 2, (float)(currentPoint.Y + PreviousPoint.Y) / 2);
CurrentPath.AddQuadCurveToPoint(newPoint, PreviousPoint);
PreviousPoint = (PointF)currentPoint;
}
else
{
CurrentPath.AddLineTo(currentPoint);
}
InvokeOnMainThread(SetNeedsDisplay);
}
public override void TouchesEnded(NSSet touches, UIEvent evt)
{
InvokeOnMainThread(SetNeedsDisplay);
}
public override void TouchesCancelled(NSSet touches, UIEvent evt)
{
InvokeOnMainThread(SetNeedsDisplay);
}
public override void Draw(CGRect rect)
{
foreach (VESLine p in Lines)
{
p.Color.SetStroke();
p.Path.Stroke();
}
}
public void LoadImageFromFile()
{
if (ImageFilePath != null && ImageFilePath != "")
{
_image = ImageHelper.GetRotateImage(ImageFilePath);
}
}
public UIImage GetImageFromView()
{
RectangleF rect;
rect = (RectangleF)Frame;
UIGraphics.BeginImageContext(rect.Size);
CGContext context = UIGraphics.GetCurrentContext();
if (_image != null)
context.DrawImage(Frame, _image.CGImage);
this.Layer.RenderInContext(context);
UIImage image = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return image;
}
}
}
ImageHelper.cs
using System;
using System.Drawing;
using CoreGraphics;
using Foundation;
using UIKit;
namespace SignaturePadDemo.iOS
{
public class ImageHelper
{
public static UIImage GetRotateImage(String imagePath)
{
UIImage image = UIImage.FromFile(imagePath);
CGImage imgRef = image.CGImage;
float width = imgRef.Width;
float height = imgRef.Height;
CGAffineTransform transform = CGAffineTransform.MakeIdentity();
RectangleF bounds = new RectangleF(0, 0, width, height);
SizeF imageSize = new SizeF(width, height);
float boundHeight;
UIImageOrientation orient = image.Orientation;
switch (orient)
{
case UIImageOrientation.Up:
transform = CGAffineTransform.MakeIdentity();
break;
case UIImageOrientation.UpMirrored:
transform = CGAffineTransform.MakeTranslation(imageSize.Width, 0.0f);
transform.Scale(-1.0f, 1.0f);
break;
case UIImageOrientation.Down:
transform.Rotate((float)Math.PI);
transform.Translate(imageSize.Width, imageSize.Height);
break;
case UIImageOrientation.DownMirrored:
transform = CGAffineTransform.MakeTranslation(0.0f, imageSize.Height);
transform.Scale(1.0f, -1.0f);
break;
case UIImageOrientation.LeftMirrored:
boundHeight = bounds.Size.Height;
bounds.Height = bounds.Size.Width;
bounds.Width = boundHeight;
transform.Scale(-1.0f, 1.0f);
transform.Rotate((float)Math.PI / 2.0f);
break;
case UIImageOrientation.Left:
boundHeight = bounds.Size.Height;
bounds.Height = bounds.Size.Width;
bounds.Width = boundHeight;
transform = CGAffineTransform.MakeRotation((float)Math.PI / 2.0f);
transform.Translate(imageSize.Height, 0.0f);
break;
case UIImageOrientation.RightMirrored:
boundHeight = bounds.Size.Height;
bounds.Height = bounds.Size.Width;
bounds.Width = boundHeight;
transform = CGAffineTransform.MakeTranslation(imageSize.Height, imageSize.Width);
transform.Scale(-1.0f, 1.0f);
transform.Rotate(3.0f * (float)Math.PI / 2.0f);
break;
case UIImageOrientation.Right:
boundHeight = bounds.Size.Height;
bounds.Height = bounds.Size.Width;
bounds.Width = boundHeight;
transform = CGAffineTransform.MakeRotation(-(float)Math.PI / 2.0f);
transform.Translate(0.0f, imageSize.Width);
break;
default:
break;
}
UIGraphics.BeginImageContext(bounds.Size);
CGContext context = UIGraphics.GetCurrentContext();
context.ConcatCTM(transform);
context = UIGraphics.GetCurrentContext();
context.DrawImage(new RectangleF(0, 0, width, height), imgRef);
UIImage imageCopy = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return imageCopy;
}
public static bool SaveRotateImage(String imagePath)
{
UIImage imageCopy = GetRotateImage(imagePath);
NSData data = imageCopy.AsJPEG();
NSError error = new NSError();
bool bSuccess = data.Save(imagePath, true, out error);
return bSuccess;
}
}
}
VESLine.cs
using System;
using UIKit;
namespace SignaturePadDemo.iOS
{
public class VESLine
{
public UIBezierPath Path
{
get;
set;
}
public UIColor Color
{
get;
set;
}
public byte Index
{
get;
set;
}
}
}
You can download from here

Placing a SearchBar in the top/navigation bar

In a Forms project, is it possible to place a SearchBar such that it appears in the top/navigation bar of the app? What I want to achieve is something along the lines of the Android Youtube app, just cross-platform:
To do this, you should write a renderer for your Page
There is my implementation for iOS (with custom 'searchField')
using CoreGraphics;
using Foundation;
using MyControls;
using MyRenderer;
using UIKit;
using Xamarin.Forms;
[assembly: ExportRenderer(typeof(MySearchContentPage), typeof(MySearchContentPageRenderer))]
namespace IOS.Renderer
{
using Xamarin.Forms.Platform.iOS;
public class MySearchContentPageRenderer : PageRenderer
{
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
SetSearchToolbar();
}
public override void WillMoveToParentViewController(UIKit.UIViewController parent)
{
base.WillMoveToParentViewController(parent);
if (parent != null)
{
parent.NavigationItem.RightBarButtonItem = NavigationItem.RightBarButtonItem;
parent.NavigationItem.TitleView = NavigationItem.TitleView;
}
}
private void SetSearchToolbar()
{
var element = Element as MySearchContentPage;
if (element == null)
{
return;
}
var width = NavigationController.NavigationBar.Frame.Width;
var height = NavigationController.NavigationBar.Frame.Height;
var searchBar = new UIStackView(new CGRect(0, 0, width * 0.85, height));
searchBar.Alignment = UIStackViewAlignment.Center;
searchBar.Axis = UILayoutConstraintAxis.Horizontal;
searchBar.Spacing = 3;
var searchTextField = new MyUITextField();
searchTextField.BackgroundColor = UIColor.FromRGB(239, 239, 239);
NSAttributedString strAttr = new NSAttributedString("Search", foregroundColor: UIColor.FromRGB(146, 146, 146));
searchTextField.AttributedPlaceholder = strAttr;
searchTextField.SizeToFit();
// Delete button
UIButton textDeleteButton = new UIButton(new CGRect(0, 0, searchTextField.Frame.Size.Height + 5, searchTextField.Frame.Height));
textDeleteButton.Font = UIFont.FromName("FontAwesome", 18f);
textDeleteButton.BackgroundColor = UIColor.Clear;
textDeleteButton.SetTitleColor(UIColor.FromRGB(146, 146, 146), UIControlState.Normal);
textDeleteButton.SetTitle("\uf057", UIControlState.Normal);
textDeleteButton.TouchUpInside += (sender, e) =>
{
searchTextField.Text = string.Empty;
};
searchTextField.RightView = textDeleteButton;
searchTextField.RightViewMode = UITextFieldViewMode.Always;
// Border
searchTextField.BorderStyle = UITextBorderStyle.RoundedRect;
searchTextField.Layer.BorderColor = UIColor.FromRGB(239, 239, 239).CGColor;
searchTextField.Layer.BorderWidth = 1;
searchTextField.Layer.CornerRadius = 5;
searchTextField.EditingChanged += (sender, e) =>
{
element.SetValue(MySearchContentPage.SearchTextProperty, searchTextField.Text);
};
searchBar.AddArrangedSubview(searchTextField);
var searchbarButtonItem = new UIBarButtonItem(searchBar);
NavigationItem.SetRightBarButtonItem(searchbarButtonItem, true);
NavigationItem.TitleView = new UIView();
if (ParentViewController != null)
{
ParentViewController.NavigationItem.RightBarButtonItem = NavigationItem.RightBarButtonItem;
ParentViewController.NavigationItem.TitleView = NavigationItem.TitleView;
}
}
}
}
Also, there is some discussion:How to include view in NavigationBar of Xamarin Forms?
I hope, you understood the main idea.

Resources