#Xamarin Forms Currency Entry Field

images.jpgI needed a simple Entry field in Xamarin Forms which would work as a currency field. It should show 0.00 when empty, but then clear out automatically once the user tapped it. I ended up using a simple behavior to accomplish it in a very simply way.

currency.gif

Here’s how:

Create a file called CurrencyBehavior.cs in your shared project:

public class CurrencyBehavior : Behavior<Entry>
    {
	    private bool _hasFormattedOnce = false;
	    protected override void OnAttachedTo(Entry entry)
	    {
		    entry.TextChanged += OnEntryTextChanged;
			entry.Focused += EntryOnFocused;
			entry.Unfocused += EntryOnUnfocused;
		    base.OnAttachedTo(entry);
	    }
 
	    private void EntryOnUnfocused(object senderFocusEventArgs e)
	    {
		    var entry = sender as Entry;
		    if (entry?.Text.HasValues()==false)
		    {
			    entry.Text = "0.00";
		    } 
	    }
 
	    private void EntryOnFocused(object senderFocusEventArgs e)
	    {
		    var entry =  sender as Entry;
		    if (entry?.Text == "0.00")
		    {
			    entry.Text = "";
		    }
	    }
 
	    protected override void OnDetachingFrom(Entry entry)
	    {
		    entry.TextChanged -= OnEntryTextChanged;
		    entry.Focused -= EntryOnFocused;
		    entry.Unfocused -= EntryOnUnfocused;
		    base.OnDetachingFrom(entry);
	    }
 
	    private   void OnEntryTextChanged(object senderTextChangedEventArgs args)
	    {
		    if (!_hasFormattedOnce && args.NewTextValue == "0")
		    {
			    ((Entrysender).Text = "0.00";
			    _hasFormattedOnce = true;
		    }
	    }
 
 
    }

Now, on your Entry control, add the behavior:

	            <Entry 
	                   Text="{Binding MyMoneyPropertyInMyViewModel}"
	                   Keyboard="Numeric">
		            <Entry.Behaviors>
			            <behaviors:CurrencyBehavior />
		            </Entry.Behaviors>
	            </Entry>

 

Advertisements

#Xamarin Forms Shell – How to make it work on iOS 9

images.jpg Xamarin Forms Shell is a great way to reduce your code complexity. It handles a lot of plumbing for you. Highly recommended; I use it in my 50,000 user app.

However, it doesn’t work on iOS 9 as of version 4.1. This article will show you how to get it to work on iOS 9

Credits: Based on discussions from a GitHub forum, code is taken from there so all create to the forum contributors listed there. Thanks! https://github.com/JRPMike

What’s the problem:

Tabs. iOS 9 doesn’t allow certain colorings of tabs (UnselectedItemTintColor), so we need to ensure that we don’t ask for a color to be applied when we are in iOS 9

We are going to add a ShellRenderer so that in iOS we can call the Tab colorings only when we are in iOS 10 and above.

Add a file called MyShellRenderer.cs to your iOS project only.

Then add this:

[assemblyExportRenderer(typeof(Shell), typeof(App.iOS.Renderers.MyShellRenderer))]
namespace App.iOS.Renderers
{

    public class MyShellRenderer : ShellRenderer
    {
 
        protected override void OnElementSet(Shell element)
        {
            base.OnElementSet(element);
        }
 
        protected override IShellTabBarAppearanceTracker CreateTabBarAppearanceTracker()
        {
	        return new MySafeShellTabBarAppearanceTracker();
        }
    
 
        
    }
 
	public class MySafeShellTabBarAppearanceTracker : IShellTabBarAppearanceTracker
	{
		UIColor _defaultBarTint;
		UIColor _defaultTint;
		UIColor _defaultUnselectedTint;
 
		public void ResetAppearance(UITabBarController controller)
		{
			if (_defaultTint == null)
				return;
 
			var tabBar = controller.TabBar;
			tabBar.BarTintColor = _defaultBarTint;
			tabBar.TintColor = _defaultTint;
			tabBar.UnselectedItemTintColor = _defaultUnselectedTint;
		}
 
		public void SetAppearance(UITabBarController controllerShellAppearance appearance)
		{
			IShellAppearanceElement appearanceElement = appearance;
			var backgroundColor = appearanceElement.EffectiveTabBarBackgroundColor;
			var foregroundColor = appearanceElement.EffectiveTabBarForegroundColor// currently unused
			var disabledColor = appearanceElement.EffectiveTabBarDisabledColor// unused on iOS
			var unselectedColor = appearanceElement.EffectiveTabBarUnselectedColor;
			var titleColor = appearanceElement.EffectiveTabBarTitleColor;
 
			var tabBar = controller.TabBar;
			bool operatingSystemHasUnselectedTint = UIDevice.CurrentDevice.CheckSystemVersion(10, 0);
			if (_defaultTint == null)
			{
				_defaultBarTint = tabBar.BarTintColor;
				_defaultTint = tabBar.TintColor;
				if (operatingSystemHasUnselectedTint)
				{
					_defaultUnselectedTint = tabBar.UnselectedItemTintColor;
				}
			}
 
			if (!backgroundColor.IsDefault)	tabBar.BarTintColor = backgroundColor.ToUIColor();
			if (!titleColor.IsDefault)	tabBar.TintColor = titleColor.ToUIColor();
			if (operatingSystemHasUnselectedTint)
			{
				if (!unselectedColor.IsDefault)
					tabBar.UnselectedItemTintColor = unselectedColor.ToUIColor();
			}
		}
 
		public void UpdateLayout(UITabBarController controller)
		{
		}
 
		#region IDisposable Support
 
		protected virtual void Dispose(bool disposing)
		{
		}
 
		public void Dispose()
		{
			Dispose(true);
		}
 
		#endregion
	}
}

 

#Xamarin.Forms: Remote Image Markup Extension

downloadI had a requirement to have an Image get its source from a URL. I didn’t want to hard code it as I wanted a central place to manage them in case they did change in the future.

I decided to write a simple MarkupExtension so that I could pass in an Enum and get a Source for the Image, like this:

<Image Source="{local:RemoteImages Login_Help_Live}" />

First, we create the Enum to indicate which image to use:

public enum EnumRemoteImages
{
    Login_Help_Live ,
    Login_Help_OnPrem,
    Multiple_tab_coachmark ,
    Multiple_swipe_coachmark,
    Notifications_coachmark,
    Todays_Business_coachmark
}

Our MarkupExtension will be used by the Source property on the Image tag, so we indicate it:

[ContentProperty("Source")]
public class RemoteImagesExtension : IMarkupExtension

And offer a reference:

public EnumRemoteImages Source { get; set; }

Then we provide the logic which simply matches an enum with an image:

public object ProvideValue(IServiceProvider serviceProvider)
        {
            ImageSource s = null;
            string url = GetImageUrlFor(Source);
            if (url.IsValidUrl())
                s = new UriImageSource()
                {
                  Uri = new Uri(url)
                };
            
            return s;
        }
 
        public static string GetImageUrlFor(EnumRemoteImages image)
        {
            string url = "https://i.ibb.co/2gcHxdg/Small-Logo.png";
            switch (image)
            {
                case EnumRemoteImages.Login_Help_Live:
                    url = "https://i.ibb.co/JdNPS4Y/login-help-live.png";
                    break;
                case EnumRemoteImages.Login_Help_OnPrem:
                    url = "https://i.ibb.co/bLbKCDS/login-help-onprem.png";
                    break;
 
                case EnumRemoteImages.Multiple_tab_coachmark:
                    url = "https://i.ibb.co/tZ9nKDt/multiple-tab-coachmark.png";
                    break;
                case EnumRemoteImages.Multiple_swipe_coachmark:
                    url = "https://i.ibb.co/XzkWX01/filter-swipe-coachmark.png";
                    break;
                case EnumRemoteImages.Notifications_coachmark:
                    url = "https://i.ibb.co/Zchr0Dq/notifications-coachmark.png";
                    break;
                case EnumRemoteImages.Todays_Business_coachmark:
                    url = "https://i.ibb.co/QMS8Fc2/todays-business-coachmark.png";
                    break;
 
            }
 
            return url;
        }

The final full class looks like this:

[ContentProperty("Source")]
    public class RemoteImagesExtension : IMarkupExtension
    {
 
        public EnumRemoteImages Source { getset; }
 
        public object ProvideValue(IServiceProvider serviceProvider)
        {
            ImageSource s = null;
            string url = GetImageUrlFor(Source);
            if (url.IsValidUrl())
                s = new UriImageSource()
                {
                  Uri = new Uri(url)
                };
            
            return s;
        }
 
        public static string GetImageUrlFor(EnumRemoteImages image)
        {
            string url = "https://i.ibb.co/2gcHxdg/Small-Logo.png";
            switch (image)
            {
                case EnumRemoteImages.Login_Help_Live:
                    url = "https://i.ibb.co/JdNPS4Y/login-help-live.png";
                    break;
                case EnumRemoteImages.Login_Help_OnPrem:
                    url = "https://i.ibb.co/bLbKCDS/login-help-onprem.png";
                    break;
 
                case EnumRemoteImages.Multiple_tab_coachmark:
                    url = "https://i.ibb.co/tZ9nKDt/multiple-tab-coachmark.png";
                    break;
                case EnumRemoteImages.Multiple_swipe_coachmark:
                    url = "https://i.ibb.co/XzkWX01/filter-swipe-coachmark.png";
                    break;
                case EnumRemoteImages.Notifications_coachmark:
                    url = "https://i.ibb.co/Zchr0Dq/notifications-coachmark.png";
                    break;
                case EnumRemoteImages.Todays_Business_coachmark:
                    url = "https://i.ibb.co/QMS8Fc2/todays-business-coachmark.png";
                    break;
 
            }
 
            return url;
        }
    }

 

#Xamarin.Forms: Using Google Maps instead of Apple Maps

download.jpgIf your user has Google Maps installed on their phone, especially for your iPhone users, they probably want to use that instead of Apple Maps. When your app needs to open Maps, it should open Google Maps in this case.

Here’s how to force the use of Google Maps in Xamarin Forms.

First, install the Xamarin.Essentials NuGet into all your projects first.

When Google Maps is installed, it tells the OS that it’s there and it can be called. To determine if it’s there, we are going to check for it using Xamarin.Essentials:

var supportsUri = await Launcher.CanOpenAsync("comgooglemaps://");

If we find it’s there, then we can call it:

await Launcher.OpenAsync($"comgooglemaps://?center={location.Latitude},{location.Longitude}&zoom=12");

If not then we can just open the default Maps for the OS:

await Xamarin.Essentials.Map.OpenAsync(location, options);

Here’s the full code:

        public static async Task ShowMap(Location locationstring title = null)
        {
            if (location != null)
            {
                var supportsUri = await Launcher.CanOpenAsync("comgooglemaps://");
                if (supportsUri)
                {
                    await Launcher.OpenAsync($"comgooglemaps://?center={location.Latitude},{location.Longitude}&zoom=12");
                }
                else
                {
	                var options = new MapLaunchOptions { Name = title??"Address" };
                   await Xamarin.Essentials.Map.OpenAsync(locationoptions);
                }
            }
        }

 

Google Maps is beloved. Let your users continue to enjoy it.

#Xamarin.Forms Simple Dependency Injection (very simple #IoC)

2019-08-01 09_05_23-injection - Google SearchYou should use Dependency Injection as a design principle (aka IoC).  Here’s why. But you are here because you already want to and understand the need and benefits. Here’s how to in Xamarin Forms, simply.

First, go to NuGet and install Simple Injector into the main project of your solution. I’ve used this on projects with hundreds of thousands of uses and it works brilliantly in Xamarin.

We will need an Interface file and a class that implements that Interface:

    public interface IAnimal
    {
	    bool CanEatGrass();
    }
 
    public class DogIAnimal
    {
	    public bool CanEatGrass()
	    {
		    return false;
	    }
    }

Add a public static class to your App.xaml.cs so that you can access your IoC:

private static Container ioCContainer = new SimpleInjector.Container();
public static Container IoCContainer
{
	get => ioCContainer;
	set => ioCContainer = value;
}

Go to your App.xaml.cs file’s constructor and tell Simple Injector what kind of animal to get when an animal is requested:

App.IoCContainer.Register<IAnimalDog>(Lifestyle.Transient);


Now we are all wired up, let’s use it.

In your Pages.xaml.cs files’ constructors you usually create your model, in our case a Dog model. You can use the IoC to create it…and more importantly, any interfaces in the Dog’s constructor will be created too, so if you have dozens of tools like Loggers and Data layers, etc, they will all be created, and all of their dependencies and on and on. One line of code now:

BindingContext = App.IoCContainer.GetInstance<IAnimal>();

Done. Wasn’t that simple?

#Xamarin Forms: What is the Font Name in iOS for a Style?

download.pngIn Xamarin Forms you can use your own fonts, but there is a trick. In iOS you need to know the name of the font, which may not be obvious.

In this Style example, you can see that I am referencing the FontAwesome Solid font. Notice the dash and how different it is in Android.

<OnPlatform x:Key="FontAwesomeSolidFontFamily" x:TypeArguments="x:String"
            Android="fa-solid-900.ttf#Font Awesome 5 Pro Solid"
            iOS="FontAwesome5Pro-Solid" />

To get the name needed for iOS, which is different from Android, use this bit of code in your iOS AppDelegate.cs (Don’t forget to remove it when done)

foreach (var familyNames in UIFont.FamilyNames.OrderBy(c => c).ToList())
{
    Console.WriteLine(" * " + familyNames);
    foreach (var familyName in UIFont
           .FontNamesForFamilyName(familyNames)
           .OrderBy(c => c).ToList())
    {
        Console.WriteLine(" *-- " + familyName);
    }
}

When run, open the Output window and you’ll see all the names off the fonts on the device and those you added in your info.plist.

Here’s my info.plist for FontAwesome Solid.

	<key>UIAppFonts</key>
	<array>
		<string>Font Awesome 5 Pro-Light-300.otf</string>
		<string>Font Awesome 5 Pro-Solid-900.otf</string>
	</array>

 

Xamarin.Forms: Fast HtmlLabel with support for RTL languages

1_28-1lYrYTQoLhi87mllgBw.pngHere’s a really simple, and fast rendering HTML label control for Xamarin Forms. It focuses on speed and only implementing a few common HTML elements.

Supports all human languages including Right-To-Left languages like Arabic and Persian.

To your main project add a new HtmlLabel class, renderers will handle the rest in the Android and iOS project:

namespace App.Views.Common
{
    public class HtmlLabel :Label
    {
        
    }
}

In iOS, add this Renderer:

[assemblyExportRenderer(typeof(HtmlLabel), typeof(HtmlLabelRenderer))]
 
namespace App.iOS.Renderers
{
    public class HtmlLabelRenderer : LabelRenderer
    {
        private HtmlLabel _element//Holds the view for later re-use
 
        protected override void OnElementPropertyChanged(object senderSystem.ComponentModel.PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sendere);
            if (this.Element == null || this.Control == null)
                return;
 
	    //Watch for changes in common properties
            if (e.PropertyName == Label.TextProperty.PropertyName || 
                e.PropertyName == Label.FontAttributesProperty.PropertyName ||
                e.PropertyName == Label.FontFamilyProperty.PropertyName || 
                e.PropertyName == Label.FontSizeProperty.PropertyName ||
                e.PropertyName == Label.TextColorProperty.PropertyName 
                )
            {
                Update();
            }
        }
 
        protected override void OnElementChanged(ElementChangedEventArgs<Labele)
        {
            base.OnElementChanged(e);
 
            if (Element is HtmlLabel view)
            {
	            view.FontFamily = "Arial"//Update with your font.
	            _element = view;   //Hold on to the element
	            Update();
            }
        }
 
        private void Update()
        {
            var attr = new NSAttributedStringDocumentAttributes();
            var nsError = new NSError();
            attr.DocumentType = NSDocumentType.HTML;
 
            if (_element is HtmlLabel htmlLabel)
            {
                var fontFamily = htmlLabel.FontFamily??"Arial";
                var fontSize = htmlLabel.FontSize;
                var text = htmlLabel.Text;
                var color = htmlLabel.TextColor;
                
                //Default to Left To Right
                var dir = "ltr";
                if (text.HasValues() && ContainsArabicOrPersianCharacter(text))
                {
                    dir = "rtl";  //Arabic or Persian
                }
 
				//Render the string with left to right or right to left support
                string html = $"\"font-family:{fontFamily};font-size:{fontSize};color:{color.ToHexString()};\">{text}";
                var myHtmlData = NSData.FromString(htmlNSStringEncoding.Unicode);
                Control.AttributedText = new NSAttributedString(myHtmlDataattrref nsError);
            }
        }
 
        static bool ContainsArabicOrPersianCharacter(string s)
        {
	        byte[] bytes = Encoding.Unicode.GetBytes(s.SafeSubstring(s.Length/2,5));  //Sample half way in the string then see if any are arabic code page
            for (int i = 1; i < bytes.Lengthi += 2)
                if (bytes[i] == 6) //0x06** is arabic/persian code page
                    return true;
            return false;
        }
    }

And in Android:

[assemblyExportRenderer(typeof(HtmlLabel), typeof(HtmlLabelRenderer))]
namespace App.Droid.Renderers
{
    public class HtmlLabelRenderer : LabelRenderer
    {
		private static bool _canUseHtml = true;	 //Android 5 and below can't
        private HtmlLabel _element;	 //Hold on to the element
        public HtmlLabelRenderer(Context c): base(c)
        {
           
        }
 
        protected override void OnElementPropertyChanged(object senderSystem.ComponentModel.PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sendere);
            if (this.Element == null || this.Control == null)
                return;
 
			//Watch these properties
            if (e.PropertyName == Label.TextProperty.PropertyName ||
                e.PropertyName == Label.FontAttributesProperty.PropertyName ||
                e.PropertyName == Label.FontFamilyProperty.PropertyName ||
                e.PropertyName == Label.FontSizeProperty.PropertyName ||
                e.PropertyName == Label.TextColorProperty.PropertyName)
            {
                Update();
            }
        }
 
        protected override void OnElementChanged(ElementChangedEventArgs<Labele)
        {
            base.OnElementChanged(e);
 
            if (Element is HtmlLabel view)
            {
	            _element = view;  //Hold on the the element
	            Update();
            }
        }
 
        private void Update()
        {
            if (_element is HtmlLabel htmlLabel)
            {
                var fontFamily = htmlLabel.FontFamily;
                var fontSize = htmlLabel.FontSize;
                var text = htmlLabel.Text;
                var color = htmlLabel.TextColor;
                try
                {
 
	           //Watch for Right to Left languages
                    var dir = "ltr";
                    if (text.HasValues() && ContainsArabicOrPersianCharacter(text))
                    {
                        dir = "rtl";
                    }
 
		    //SEE:
                    //https://www.grokkingandroid.com/android-quick-tip-formatting-text-with-html-fromhtml/
                    string html = $"<html dir='{dir}'><body><font style=\"face:{fontFamily};size:{fontSize};color:{color.ToHexString()}\">{text}</font></body></html>";
 
		 //If we ever fail, it means the Android version can't use HTML labels (Android 5)
		 //so gracefully downgrade
                    if (_canUseHtml)
                    {
	                    Control.SetText(Html.FromHtml(htmlFromHtmlOptions.OptionUseCssColors), TextView.BufferType.Spannable);
                    }
                    else
                    {
	                    Control.SetText(text.StripHtml(false), TextView.BufferType.Normal);
                    }
                }
                catch  //Failed so this version of android can't
                {
	                _canUseHtml = false;  //Android 5 and below - don't try again
                    Control.SetText(text.StripHtml(false),TextView.BufferType.Normal);
                }
            }
        }
 
        static bool ContainsArabicOrPersianCharacter(string s)
        {
            byte[] bytes = Encoding.Unicode.GetBytes(s.SafeSubstring(s.Length / 2, 5)); //Sample half way in the string then see if any are arabic code page
            for (int i = 1; i < bytes.Lengthi += 2)
                if (bytes[i] == 6) //0x06** is arabic code page
                    return true;
            return false;
        }
    }