4
votes

I would like to use SF Pro (iOS) and Roboto (Android) which are the system fonts. Both have medium but I cannot see any way to set these for a label.

I think I might need a Label custom renderer and would appreciate any advice / suggestions.

Here are some references:

https://qiita.com/aqubi/items/3a1e668f4f78d4ad0db7

https://forums.xamarin.com/discussion/97446/set-font-weight-on-label

https://github.com/xamarin/Xamarin.Forms/issues/8035

2
Is the issue only for iOS? Also, how are you trying to set the SemiBold family?Mihail Duchev
I would like to know how to set for both iOS and Android.Alan2

2 Answers

5
votes

Let's start with Android - you can't set Roboto font to semi-bold, since the font doesn't support this weight. Link for reference - Roboto - Google Fonts.

For iOS, San Francisco supports this font weight. Since you are using the built-in system fonts, simply setting the value to .SFUIText-Semibold should do the trick.

UPDATE

Due to the updated nature of the question, we can have 2 approaches here for iOS. The warning message, that you are getting, says it all:

CoreText note: Client requested name ".SFUIText-Medium", it will get TimesNewRomanPSMT rather than the intended font. All system UI font access should be through proper APIs such as CTFontCreateUIFontForLanguage() or +[UIFont systemFontOfSize:].

This means that in our render, we can set the font like this:

Control.Font = UIFont.SystemFontOfSize(17, UIFontWeight.Medium);

The warning message also says that it can be achieved with CTFont like so:

new CTFont(".SFUIText-Medium", 17);

For iOS, it is preferrable to use the first approach with SystemFontOfSize.

NB: Keep in mind that this may not remove the warning message, since very often they are caused from 3rd-party packages, which are still refering to an old Forms version, or are setting the font incorrectly. If this happens, inspect your packages and raise an issue, if necessary. You can take a look at the official commit that fixed this (link). As you can see, they are splitting the string that you've provided and taking the last part. If the last part can be converter to UIFontWeight, they are setting the font the same way as I have just described. So, in the end, if you provide the font as .SFUIText-Medium in your shared project, it will have the same effect as explicitly refering to it in your custom renderer, because both approaches end up with the same code. Again, you'd want to check your 3rd-party libraries, if the warning persists.

Here's a screenshot with both results - setting the font in the shared project & setting it with a renderer: iOS

For Android you can again set the font in a renderer. Here, you'll need a custom style for this one. In your Android project, create a new style in Resources/values/styles.xml like this:

<style name="FontRobotoMedium">
  <item name="android:fontFamily">sans-serif-medium</item>
  <item name="android:textColor">#333333</item>
  <item name="android:textSize">17sp</item>
</style>

You can customize a lot here, but for this sample, I have simply addded the family, size & color.

Then, in your renderer, simply change the control's TextAppearance:

Control.SetTextAppearance(Resource.Style.FontRobotoMedium);

Keep in mind that this can also be achieved without any renderers utilising the power of the system built-in fonts like this:

<Label FontFamily="sans-serif-medium" />

For reference of Android's system fonts, see here.

NB: Keep in mind that Android system fonts are also a subject to a frequent change, so it is again not advisable to use the fonts like this. There's a huge warning at the beginning of fonts.xml file that I have given a reference to:

WARNING: Parsing of this file by third-party apps is not supported. The file, and the font files it refers to, will be renamed and/or moved out from their respective location in the next Android release, and/or the format or syntax of the file may change significantly. If you parse this file for information about system fonts, do it at your own risk. Your application will almost certainly break with the next major Android release.

Here's a screenshot with both results - setting the font in the shared project & setting it with a renderer: android NOTE: The fonts defer a bit due to a slight difference in the text padding, which can be adjusted in the style like this:

<item name="android:padding">1dip</item>

To sum up, what you can do with a custom renderer, can also be done in the shared project. Under all of the code, the team from Xamarin is doing it very similar (if not the same), as the code from the custom renderer. Don't expect all of the warnings to be gone, since if any 3rd-party library hasn't ungraded to latest forms, or is using the system fonts incorrectly, the warnings will still appear.

Again, it is really not a good practice to try to use the system fonts. They are reserved (and in the case of iOS - hidden behind a dot (.) name) for a reason. In one or two updates, the font may changed and will break the appearance of your app. If you don't have any explicit reason to not import the font files, I'd strongly suggest that you import the font files are refer to them for their families. If you still want to use the system fonts, you can achieve this without any renderers. For iOS simply set the family to .SFUIText-Medium and for Android - to sans-serif-medium (both in your shared project).

UPDATE 2

Let's say that you'll set the family as an enum in your shared project. The enum can look like this:

public enum FontFamilyType
{
    Light,
    Regular,
    Medium
}

You can add one additional property in your CustomLabel wrapper - e.g.

public FontFamilyType FontFamilyType { get; set; }

You can make it bindable property, if necessary.

Here are 2 renderers that take change the font to Regular, Light & Medium. For iOS:

[assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))]
namespace FontRenderers.iOS
{
    public class CustomLabelRenderer : LabelRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
        {
            base.OnElementChanged(e);

            if (Control != null && Element != null)
            {
                UIFont font;

                switch (Element.FontFamilyType)
                {
                    case FontFamilyType.Light:
                        font= UIFont.SystemFontOfSize(18, UIFontWeight.Light);
                        break;
                    case FontFamilyType.Regular:
                        font= UIFont.SystemFontOfSize(18, UIFontWeight.Regular);
                        break;
                    case FontFamilyType.Medium:
                        font= UIFont.SystemFontOfSize(18, UIFontWeight.Medium);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }

                Control.Font = font;
            }
        }
    }
}

Here I have given the example with all of the 3 requested font families. You can switch them however you'd like - manually, checking some parameters, etc.

For Android:

[assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))]
namespace FontRenderer.Droid
{
    public class CustomLabelRenderer : LabelRenderer
    {
        public CustomLabelRenderer(Context context)
            : base(context)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
        {
            base.OnElementChanged(e);

            int fontFamilyResId;

            switch (Element.FontFamilyType)
            {
                case FontFamilyType.Light:
                    fontFamilyResId = Resource.Style.FontRobotoLight;
                    break;
                case FontFamilyType.Regular:
                    fontFamilyResId = Resource.Style.FontRobotoRegular;
                    break;
                case FontFamilyType.Medium:
                    fontFamilyResId = Resource.Style.FontRobotoMedium;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }


            Control.SetTextAppearance(fontFamilyResId);
        }
    }
}

Of course, for Android you'll need 2 more styles (for the light & regular families). In styles.xml add these 2 styles:

<style name="FontRobotoLight">
  <item name="android:fontFamily">sans-serif-light</item>
</style>

<style name="FontRobotoRegular">
  <item name="android:fontFamily">sans-serif</item>
</style>

1
votes

Is this what you want? It is not recommend to use the name of font as it is private and may change in the future.

Here is the code for customLabel:

public class customLabel : Label{

    public int myFont { get; set; }
}

And in Xaml:

<StackLayout>
    <!-- Place new controls here -->
    <Label Text="Welcome to Xamarin.Forms!" 
       HorizontalOptions="Center"
       VerticalOptions="CenterAndExpand" />

    <app307:customLabel Text="testText" myFont="15"
       HorizontalOptions="Center"
       VerticalOptions="CenterAndExpand" />
</StackLayout>

Here is the code of Renderer

[assembly: ExportRenderer (typeof(customLabel), typeof(MyLabelRenderer))]
namespace App307.iOS
{
    public class MyLabelRenderer : LabelRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
        {
            base.OnElementChanged(e);

            int fontSize = 18;

            if (e.NewElement != null)
            {
                customLabel label = e.NewElement as customLabel;

                fontSize = label.myFont;
            }

            if (Control != null)
            {
       
                UIFont font = UIFont.SystemFontOfSize(fontSize, UIFontWeight.Medium);
                Control.Font = font;
            }
        }
    }
}

enter image description here

Refer: use new San Francisco font