Custom pin on xamarin forms map - xamarin

I have a Xamarin Forms project which requires a custom map pin. I have the stub defined in the PCL with a couple of additional properties (for lat and long) defined within the class that inherits the map.
The map shows up fine, but the custom image pin only shows as the standard red pin.
Below is my custom renderer for the map (iOS version). From what I can see on various forums, this should work fine.
public class CustomMapRenderer : ViewRenderer<CustomMap, MKMapView>
{
MKMapView mkMapView;
protected override void OnElementChanged(ElementChangedEventArgs<CustomMap> e)
{
base.OnElementChanged(e);
var map = e.NewElement;
SetNativeControl(new MKMapView(CGRect.Empty));
mkMapView = Control;
MyMapDelegate myMapDelegate = new MyMapDelegate();
mkMapView.Delegate = myMapDelegate;
mkMapView.AddAnnotation(new MKPointAnnotation()
{
Coordinate = new CLLocationCoordinate2D(map.MapPinLatitude, map.MapPinLongitude)
});
mkMapView.MapType = MKMapType.Hybrid;
mkMapView.ZoomEnabled = true;
}
}
public class MyMapDelegate : MKMapViewDelegate
{
protected string annotationIdentifier = "PinAnnotation";
public override MKAnnotationView GetViewForAnnotation(MKMapView mapView, IMKAnnotation annotation)
{
MKAnnotationView anView;
if (annotation is MKUserLocation)
return null;
// create pin annotation view
anView = (MKPinAnnotationView)mapView.DequeueReusableAnnotation(annotationIdentifier);
if (anView == null)
anView = new MKPinAnnotationView(annotation, annotationIdentifier);
anView.Image = GetImage("pinned_location.png");
anView.CanShowCallout = true;
return anView;
}
public UIImage GetImage(string imageName)
{
var image = UIImage.FromFile(imageName).Scale(new SizeF() { Height = 20, Width = 30 });
return image;
}
Can anyone suggest why I'm not seeing the custom image for the pin?

You should use MKAnnotationView instead of MKPinAnnotationView.
// create annotation view
anView = (MKAnnotationView)mapView.DequeueReusableAnnotation(annotationIdentifier);
if (anView == null)
anView = new MKAnnotationView(annotation, annotationIdentifier);
MKPinAnnotationView is only for default icon.

Related

Xamarin: Detect device rotation when orientation is locked? For iOS and/or Android?

I am working on a Xamarin.Forms app that plays videos similar to a YouTube type app. I want the video to go full screen when the device rotates (like youtube does) but I also want the orientation to be locked in to portrait. Every post or tutorial I've found points to using custom renders for detecting orientation change to determine when the device rotates, but when orientation is locked those events do not fire.
Is there a way to detect device rotation without depending on orientation changing?
On iOS you would get device orientation with:
var orientation = UIDevice.CurrentDevice.Orientation;
On Android you need to ask the Window Manager:
var windowManager = ApplicationContext.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();
var orientation = windowManager.DefaultDisplay.Rotation;
You don't need a custom renderer, but you could suffice with a service you register in the service locator. This could looks something like.
In shared code:
public enum Orientation
{
None,
PortraitUp,
PortraitDown,
LandscapeLeft,
LandscapeRight
}
public interface IOrientationService
{
Orientation GetCurrentOrientation();
}
On Android:
[assembly: Dependency(typeof(AndroidOrientationService))]
public class AndroidOrientationService : IOrientationService
{
private readonly IWindowManager _windowManager;
public AndroidOrientationService()
{
_windowManager = ApplicationContext.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();
}
public Orientation GetCurrentOrientation()
{
switch (_windowManager.DefaultDisplay.Rotation)
{
case SurfaceOrientation.Rotation0:
return Orientation.PortraitUp;
case SurfaceOrientation.Rotation180:
return Orientation.PortraitDown;
case SurfaceOrientation.Rotation90:
return Orientation.LandscapeLeft;
case SurfaceOrientation.Rotation270:
return Orientation.LandscapeRight;
default:
return Orientation.None;
}
}
}
Similarly on iOS:
[assembly: Dependency(typeof(IosOrientationService))]
public class IosOrientationService : IOrientationService
{
public Orientation GetCurrentOrientation()
{
switch (UIDevice.CurrentDevice.Orientation)
{
case UIDeviceOrientation.LandscapeLeft:
return Orientation.LandscapeLeft;
case UIDeviceOrientation.LandscapeRight:
return Orientation.LandscapeRight;
case UIDeviceOrientation.Portrait:
return Orientation.PortraitUp;
case UIDeviceOrientation.PortraitUpsideDown:
return Orientation.PortraitDown;
default:
return Orientation.None;
}
}
}
Then in your code you should be able to get the orientation like:
var orientationService = DependencyService.Get<IOrientationService>();
var orientation = orientationService.GetCurrentOrientation();
EDIT: detecting orientation changes
If you want to detect orientation changes on iOS you can do that by adding an observer for UIDeviceOrientation.
UIDevice.Notifications.ObserveOrientationDidChange(OnOrientationChanged);
Similarly on Android you can use SensorManager to listen to SensorType.Orientation changes. It has a bit more moving parts but looks something like follows.
You need to create a ISensorEventListener class:
class MyOrientationListner : Java.Lang.Object, ISensorEventListener
{
public event EventHandler OrientationChanged;
public void OnAccuracyChanged(Sensor sensor, SensorStatus accuracy)
{
}
public void OnSensorChanged(SensorEvent e)
{
OrientationChanged?.Invoke(this, EventArgs.Empty);
}
}
Then you need to get the sensor manager from the current Context and start listening to orientation change events:
_sensorManager = context.GetSystemService(Context.SensorService).JavaCast<SensorManager>();
var sensor = _sensorManager.GetDefaultSensor(SensorType.Orientation);
var listener = new MyOrientationListner();
listener.OrientationChanged += OnOrientationChanged;
_sensorManager.RegisterListener(listener, sensor, SensorDelay.Normal);
private void OnOrientationChanged(object sender, EventArgs e)
{
OrientationChanged?.Invoke(this, GetCurrentOrientation());
}
Where OrientationChanged is a event in the IOrientationService:
event EventHandler<Orientation> OrientationChanged;
Then you can listen to that event where needed.
For iOS
In AppDelegate.cs override the below method
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations(UIApplication application,UIWindow forWindow)
{
if (Xamarin.Forms.Application.Current == null || Xamarin.Forms.Application.Current.MainPage == null)
{
return UIInterfaceOrientationMask.Portrait;
}
var mainPage = Xamarin.Forms.Application.Current.MainPage;
if (mainPage is YourPage || (mainPage is NavigationPage &&
((NavigationPage)mainPage).CurrentPage is YourPage) || (mainPage.Navigation != null &&
mainPage.Navigation.ModalStack.LastOrDefault() is YourPage))
{
if (Configuration.IsFullScreen)
{
return UIInterfaceOrientationMask.Landscape;
}
}
return UIInterfaceOrientationMask.Portrait;
}
In a Dependency Service write the below method
public void ChangeLandscapeOrientation()
{
UIDevice.CurrentDevice.SetValueForKey(new NSNumber((int)UIInterfaceOrientation.LandscapeLeft), new NSString("orientation"));
UINavigationController.AttemptRotationToDeviceOrientation();
}
Call the ChangeLandscapeOrientation method wherever you need it.
For Android
In a Dependency Service write the below method to change the orientation to Landscape
public void ChangeLandscapeOrientation()
{
var activity = (Activity)Xamarin.Forms.Forms.Context;
{
activity.RequestedOrientation = ScreenOrientation.Landscape;
var attrs = activity.Window.Attributes;
_originalFlags = attrs.Flags;
attrs.Flags |= Android.Views.WindowManagerFlags.Fullscreen;
activity.Window.Attributes = attrs;
}
}
Below code to change the orientation to Portrait
public void ChangePortraitOrientation()
{
var activity = (Activity)Xamarin.Forms.Forms.Context;
{
activity.RequestedOrientation = ScreenOrientation.Portrait;
var attrs = activity.Window.Attributes;
attrs.Flags = _originalFlags;
activity.Window.Attributes = attrs;
}
}
Hope it helps!

How can I create a custom TableView that will allow me to specify footer height and text and space out the text just like in the iOS settings page?

I saw this example:
Xamarin Forms - How to create custom render to give TableSection the default iOS Footer?
It does 75% of what I am looking for with this code:
using CoreGraphics;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(TableView), typeof(Japanese.iOS.TableViewCustomRenderer))]
namespace Japanese.iOS
{
public class TableViewCustomRenderer : TableViewRenderer
{
UITableView tableView;
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
var tableView = Control as UITableView;
var formsTableView = Element as TableView;
tableView.WeakDelegate = new CustomFooterTableViewModelRenderer(formsTableView);
}
void Current_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
}
private class CustomFooterTableViewModelRenderer : TableViewModelRenderer
{
public CustomFooterTableViewModelRenderer(TableView model) : base(model)
{
}
public override nfloat GetHeightForFooter(UITableView tableView, nint section)
{
return 10;
}
public override string TitleForFooter(UITableView tableView, nint section)
{
return "This is the title for this given section";
}
}
}
}
1. However what I would like is to be able to extend TableView so that I am able to put in the XAML some way to set or leave unset the footer text and height. Something like:
<ExtTableView FooterText="abc" FooterHeight="50". ..
2. From experiments with the code above I tried hardcoding in some text and realize that there is no spacing set. So I would also like to find out if there is a way to set the spacing and font so it appears just like in the iOS settings pages?
Could someone suggest how I could go about creating what I am looking for which is I guess something like an ExtTableView class that can accept additional arguments.
As hankide said , I just provide more details.
However what I would like is to be able to extend TableView so that I am able to put in the XAML some way to set or leave unset the footer text and height.
Create MyTableView that inherits from TableView
public class MyTableView : TableView
{
public static readonly BindableProperty FooterHeightProperty =
BindableProperty.Create("FooterHeight", typeof(string), typeof(MyTableView), "");
public string FooterHeight
{
get { return (string)GetValue(FooterHeightProperty); }
set { SetValue(FooterHeightProperty, value); }
}
public static readonly BindableProperty FooterTextProperty =
BindableProperty.Create("FooterText", typeof(string), typeof(MyTableView), "");
public string FooterText
{
get { return (string)GetValue(FooterTextProperty); }
set { SetValue(FooterTextProperty, value); }
}
}
Get the value that you set in XMAL and assign them to CustomFooterTableViewModelRenderer
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
var tableView = Control as UITableView;
var formsTableView = Element as MyTableView;
CustomFooterTableViewModelRenderer render = new CustomFooterTableViewModelRenderer(formsTableView);
render.height = float.Parse(formsTableView.FooterHeight);
render.text = formsTableView.FooterText;
tableView.WeakDelegate = render;
}
private class CustomFooterTableViewModelRenderer : TableViewModelRenderer
{
public float height { get; set; }
public String text { get; set; }
public CustomFooterTableViewModelRenderer(TableView model) : base(model)
{
}
public override UIView GetViewForFooter(UITableView tableView, nint section)
{
UIView view = new UIView(new CGRect(0, 0, tableView.Frame.Width, 50));
view.BackgroundColor = UIColor.Gray;
UILabel label = new UILabel();
label.Frame = new CGRect(0, 0, tableView.Frame.Width, height);
label.BackgroundColor = UIColor.Red;
label.Text = text;
label.Font = UIFont.SystemFontOfSize(15);
view.Add(label);
return view;
}
public override nfloat GetHeightForFooter(UITableView tableView, nint section)
{
return 50;
}
}
Usage:
<local:MyTableView FooterHeight="20" FooterText="ABC">
<TableRoot>
<TableSection>
<TextCell Text="22222" ></TextCell>
</TableSection>
</TableRoot>
</local:MyTableView>
From experiments with the code above I tried hardcoding in some text and realize that there is no spacing set. So I would also like to find out if there is a way to set the spacing and font so it appears just like in the iOS settings pages?
You could override the method GetViewForFooter to change the defalut style of footer,find it in the code above .
My test :
You had the right idea about creating the custom control. Here's what to do:
Create ExtTableView class that inherits from TableView
public class ExtTableView : TableView { }
Create BindableProperties for both FooterText and FooterHeight, as outlined here.
After that you can set the properties in XAML
<ExtTableView FooterText="abc" FooterHeight="50" ...
Within the renderer, you can get the values from Element (which points to our Xamarin.Forms ExtTableView).
// Modify the native control with these values
var text = Element.FooterText;
var height = Element.FooterHeight;

ActionBar With Tabbar in Xamarin forms for all(android, iOs and WinPhone)

i need to Create a ActionBar with TabbedLayout control in xamarin forms, In xamarin Android i did that Easily but now they want in both platform IOS and Android using Xamarin forms.please share any Example or Give me suggestion for how to make the custom Controls in Xamari Froms.
Below i have attached the Image how i need Action bar with Tabbed layout.
If you are using Xamarin.Forms the Tabbed page, for Android tabbar items will in the top. For iOS, you have to create a renderer to achieve it. However, Showing Tabbar items in the top are against User guidelines of iOS.
Create custom render, override ViewDidLayoutSubviews and add the following lines code.
[assembly: ExportRenderer(typeof(ExtendedTabbedPage), typeof(ExtendedTabbedPageRenderer))]
namespace ExtendedTabbedPage.Pages
{
public class ExtendedTabbedPageRenderer : TabbedRenderer
{
private ExtendedTabbedPage Page => (ExtendedTabbedPage)Element;
public ExtendedTabbedPageRenderer()
{
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
var page = (ExtendedTabbedPage)Element;
page.CurrentPageChanged += Page_CurrentPageChanged;
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
Page_CurrentPageChanged();
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
SetTabPostition();
}
void SetTabPostition()
{
if (Element == null)
return;
var element = Element as ExtendedTabbedPage;
this.TabBar.InvalidateIntrinsicContentSize();
nfloat tabSize = 74.0f;
UIInterfaceOrientation orientation = UIApplication.SharedApplication.StatusBarOrientation;
if (UIInterfaceOrientation.LandscapeLeft == orientation || UIInterfaceOrientation.LandscapeRight == orientation)
{
tabSize = 32.0f;
}
CGRect tabFrame = this.TabBar.Frame;
CGRect viewFrame = this.View.Frame;
tabFrame.Height = tabSize;
tabFrame.Y = this.View.Frame.Y;
this.TabBar.Frame = tabFrame;
this.TabBar.ContentMode = UIViewContentMode.Top;
PageController.ContainerArea = new Rectangle(0, tabFrame.Y + tabFrame.Height, viewFrame.Width, viewFrame.Height - tabFrame.Height);
this.TabBar.SetNeedsUpdateConstraints();
}
void Page_CurrentPageChanged()
{
var current = Tabbed.CurrentPage;
//if Tab is more than 5 then more will appear in iOS
if (current == null)
{
CGRect tabFrm = this.TabBar.Frame;
if (this.MoreNavigationController != null)
{
var morenavframe = this.MoreNavigationController.View.Frame;
morenavframe.Y = tabFrm.Y + tabFrm.Height;
this.MoreNavigationController.View.Frame = morenavframe;
foreach (var morecontroller in this.MoreNavigationController.ViewControllers)
{
var morecontframe = morecontroller.View.Frame;
morecontframe.Y = morenavframe.Y + morenavframe.Height;
morecontroller.View.Frame = tabFrm;
}
}
return;
}
var controller = Platform.GetRenderer(current);
if (controller == null)
return;
var frame = controller.ViewController.View.Frame;
CGRect tabFrame = this.TabBar.Frame;
frame.Y = (tabFrame.Y + tabFrame.Height);
controller.ViewController.View.Frame = frame;
this.View.Frame = frame;
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
Page_CurrentPageChanged();
}
}
}
To get a tabbed layout in Xamarin.Forms you'll usually use a TabbedPage. This will give you the tabs you show on Android. On iOS and Windows you'll get the native alternative. This means you'll get the tabs on the bottom of the screen on iOS and on Windows you'll get the tabs on top (similar, but exactly like on Android). See this illustration from the Xamarin docs:
If you want to create your own version you can implement your own version of the MultiPage class.

Centering a UIView within a UIScrollView Xamarin.iOS

I am currently trying to center a UIIView inside of a UIScrollView and am having some difficulty in doing so.
Here is the image of my current view:
Here is the code snippet I'm working with:
public void AddView(UIViewController viewCont)
{
this.AddChildViewController(viewCont);
this.mainScrollView.AddSubview(viewCont.View);
viewCont.DidMoveToParentViewController(this);
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
var m = new Menu();
//var c = new Camera();
AddView(m);
AddView(c);
CGRect cFrame = c.View.Frame;
cFrame.X = this.View.Frame.Width;
c.View.Frame = cFrame;
this.mainScrollView.ContentSize = new CGSize(this.View.Frame.Width * 2, 1.0);
}
I want to fill this whole view with Green but as you can see, the bottom quarter of the View does not stretch all the way to the bottom.
For the time being, I have removed all constraints because every attempt in adding them results in no successes. I was hoping to get a concrete answer here as to how I could go about centering this view within this UIScrollView.
Thanks
UPDATE: 3-21-2017
My main goal is to have 2 ViewControllers side by side within my UIScrollView that I can navigate to and from using a swipe gesture, like SnapChat. Following, #Digitalsa1nt suggestions, I unfortunately come up with the same issue.
Here are some more pictures:
This first one shows what happens when I only add the 1 view:
This next one shows what happens when I try to add both views to my UIScrollView, only the camera shows:
Finally, here is the code that I am using to back my Camera view:
using Foundation;
using UIKit;
using AVFoundation;
namespace BRB.iOS
{
public partial class Camera : UIViewController
{
AVCaptureSession captureSession;
AVCaptureStillImageOutput stillImageOutput;
AVCaptureVideoPreviewLayer previewLayer;
public Camera() : base("Camera", null)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
previewLayer.Frame = cameraView.Bounds;
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
captureSession = new AVCaptureSession();
captureSession.SessionPreset = AVCaptureSession.Preset1920x1080;
var backCamera = AVCaptureDevice.GetDefaultDevice(AVMediaType.Video);
NSError error;
var input = AVCaptureDeviceInput.FromDevice(backCamera, out error);
if (error == null && captureSession.CanAddInput(input))
{
captureSession.AddInput(input);
stillImageOutput = new AVCaptureStillImageOutput();
stillImageOutput.OutputSettings = new NSDictionary(AVVideo.CodecKey, AVVideo.CodecJPEG);
if (captureSession.CanAddOutput(stillImageOutput))
{
captureSession.AddOutput(stillImageOutput);
previewLayer = new AVCaptureVideoPreviewLayer(captureSession);
previewLayer.VideoGravity = AVLayerVideoGravity.ResizeAspect;
previewLayer.Connection.VideoOrientation = AVCaptureVideoOrientation.Portrait;
cameraView.Layer.AddSublayer(previewLayer);
captureSession.StartRunning();
}
}
}
}
}
I usually override 'didlayoutsubviews' for making changes to the views within my UIScrollViews. The below worked for me.
public void AddView(UIViewController viewCont)
{
AddChildViewController(viewCont);
mainScrollView.AddSubview(viewCont.View);
viewCont.DidMoveToParentViewController(this);
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
// Ensure our contentinsets are 0 so we don't have any blank space
mainScrollView.ContentInset = new UIEdgeInsets(0, 0, 0, 0);
// set the contentsize to the bounds of the container view within.
mainScrollView.ContentSize = View.Bounds.Size;
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
var m = new Menu();
//var c = new Camera();
AddView(m);
AddView(c);
}

Custom Font in Xamarin.Forms Label with FormattedString

I have created a custom LabelRenderer in my Android app to apply a custom font in a Xamarin Android app (https://developer.xamarin.com/guides/xamarin-forms/user-interface/text/fonts/).
Everything works great for a normal label with the content added to the .Text property. However, if I create a label using .FormattedText property, the custom font is not applied.
Anyone have success doing this? An option, since I'm just stacking lines of different sized text, is to use separate label controls for each, but I'd prefer to use a formatted string if possible.
Here's the guts of my custom renderer:
[assembly: ExportRenderer (typeof (gbrLabel), typeof (gbrLabelRenderer))]
public class gbrLabelRenderer: LabelRenderer
{
protected override void OnElementChanged (ElementChangedEventArgs<Label> e)
{
base.OnElementChanged (e);
var label = (TextView)Control;
Typeface font = Typeface.CreateFromAsset (Forms.Context.Assets, "Lobster-Regular.ttf");
label.Typeface = font;
}
}
And here's my simple label control... all it does is apply the font to iOS, and leaves applying the font for Android up to the custom renderer.
public class gbrLabel: Label
{
public gbrLabel ()
{
Device.OnPlatform (
iOS: () => {
FontFamily = "Lobster-Regular";
FontSize = Device.GetNamedSize(NamedSize.Medium,this);
}
}
}
Works fine for labels with just the .Text property... but not for labels with the .FormattedText property.
Should I keep digging, or just stack my labels since that's an option in this case?
Here's an example of the various ways I've tried this in the Formatted text, since that was requested:
var fs = new FormattedString ();
fs.Spans.Add (new Span {
Text = string.Format("LINE 1\n",Title),
FontSize = Device.GetNamedSize(NamedSize.Large,typeof(Label))
});
fs.Spans.Add (new Span {
Text = string.Format ("LINE 2\n"),
FontSize = Device.GetNamedSize(NamedSize.Large,typeof(Label)) * 2,
FontAttributes = FontAttributes.Bold,
FontFamily = "Lobster-Regular"
});
fs.Spans.Add (new Span {
Text = string.Format ("LINE 3\n"),
FontSize = Device.GetNamedSize(NamedSize.Medium,typeof(Label)),
FontFamily = "Lobster-Regular.ttf"
});
gbrLabel lblContent = new gbrLabel {
FormattedText = fs
}
None of these (the first should be set by the default class / renderer, and the second 2 are variations of including the font in a span definition itself) work on Android.
Note: Android and iOS issues have been summarized on a blog post: smstuebe.de/2016/04/03/formattedtext.xamrin.forms/
The font is set as long as you do not set FontSize or FontAttributes. So I had the look at the implementation and found that the FormattedText is trying to load the font like the default renderer which doesn't work on Android.
The android formatting system works very similar to that one of Xamarin.Forms. It's using spans to define text attributes. The renderer is adding a FontSpan for every Span with a custom font, size or attribute. Unfortunately, the FontSpanclass is a private inner class of FormattedStringExtensions so we have to deal with reflections.
Our Renderer is updating the Control.TextFormatted on initialization and when the FormattedText property changes. In the update method, we get all FontSpans and replace them with our CustomTypefaceSpan.
Renderer
public class FormattedLabelRenderer : LabelRenderer
{
private static readonly Typeface Font = Typeface.CreateFromAsset(Forms.Context.Assets, "LobsterTwo-Regular.ttf");
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
Control.Typeface = Font;
UpdateFormattedText();
}
private void UpdateFormattedText()
{
if (Element.FormattedText != null)
{
var extensionType = typeof(FormattedStringExtensions);
var type = extensionType.GetNestedType("FontSpan", BindingFlags.NonPublic);
var ss = new SpannableString(Control.TextFormatted);
var spans = ss.GetSpans(0, ss.ToString().Length, Class.FromType(type));
foreach (var span in spans)
{
var start = ss.GetSpanStart(span);
var end = ss.GetSpanEnd(span);
var flags = ss.GetSpanFlags(span);
var font = (Font)type.GetProperty("Font").GetValue(span, null);
ss.RemoveSpan(span);
var newSpan = new CustomTypefaceSpan(Control, font);
ss.SetSpan(newSpan, start, end, flags);
}
Control.TextFormatted = ss;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Label.FormattedTextProperty.PropertyName)
{
UpdateFormattedText();
}
}
}
I'm not sure, why you introduced a new element type gbrLabel, but as long as you only wan't to change the renderer, you don't have to create a custom element. You can replace the renderer of the default element:
[assembly: ExportRenderer(typeof(Label), typeof(FormattedLabelRenderer))]
CustomTypefaceSpan
public class CustomTypefaceSpan : MetricAffectingSpan
{
private readonly Typeface _typeFace;
private readonly Typeface _typeFaceBold;
private readonly Typeface _typeFaceItalic;
private readonly Typeface _typeFaceBoldItalic;
private readonly TextView _textView;
private Font _font;
public CustomTypefaceSpan(TextView textView, Font font)
{
_textView = textView;
_font = font;
// Note: we are ignoring _font.FontFamily (but thats easy to change)
_typeFace = Typeface.CreateFromAsset(Forms.Context.Assets, "LobsterTwo-Regular.ttf");
_typeFaceBold = Typeface.CreateFromAsset(Forms.Context.Assets, "LobsterTwo-Bold.ttf");
_typeFaceItalic = Typeface.CreateFromAsset(Forms.Context.Assets, "LobsterTwo-Italic.ttf");
_typeFaceBoldItalic = Typeface.CreateFromAsset(Forms.Context.Assets, "LobsterTwo-BoldItalic.ttf");
}
public override void UpdateDrawState(TextPaint paint)
{
ApplyCustomTypeFace(paint);
}
public override void UpdateMeasureState(TextPaint paint)
{
ApplyCustomTypeFace(paint);
}
private void ApplyCustomTypeFace(Paint paint)
{
var tf = _typeFace;
if (_font.FontAttributes.HasFlag(FontAttributes.Bold) && _font.FontAttributes.HasFlag(FontAttributes.Italic))
{
tf = _typeFaceBoldItalic;
}
else if (_font.FontAttributes.HasFlag(FontAttributes.Bold))
{
tf = _typeFaceBold;
}
else if (_font.FontAttributes.HasFlag(FontAttributes.Italic))
{
tf = _typeFaceItalic;
}
paint.SetTypeface(tf);
paint.TextSize = TypedValue.ApplyDimension(ComplexUnitType.Sp, _font.ToScaledPixel(), _textView.Resources.DisplayMetrics);
}
}
Our Custom CustomTypefaceSpanis similar to the FontSpan of Xamarin.Forms, but is loading the custom fonts and can load different fonts for different FontAttributes.
The result is a nice colorful Text :)

Resources