.Net MAUI how to include a link in a Label
Asked Answered
T

5

8

Using Visual Studio Community edition 2022.

New to .Net MAUI and am trying to follow the examples provided in the documentation found at https://learn.microsoft.com/en-us/dotnet/maui/user-interface/controls/label

The XAML code below (from the docs) is used to create a "link" using a tap recognizer:

     <Label>
        <Label.FormattedText>
           <FormattedString>
              <Span 
                 Text="Link: " 
              />
              <Span 
                 Text="click here to open the docs"
                 TextColor="Blue"
                 TextDecorations="Underline">
                 <Span.GestureRecognizers>
                    <TapGestureRecognizer
                       Command="{Binding OpenUrlCommand}"
                       CommandParameter="https://learn.microsoft.com/dotnet/maui/" 
                    />
                 </Span.GestureRecognizers>
              </Span>
           </FormattedString>
        </Label.FormattedText>
     </Label>

However, the command (OpenUrlCommand) is never called - tested using the Windows and Andriod emulators.

In my ViewModel, I define the OpenUrlCommand as follows:

public ICommand OpenUrlCommand => new Command<string>( async ( url ) => await Launcher.OpenAsync( url ) );

... but nothing works, I also tried the following -- no go ...

public ICommand OpenUrlCommand => new Command<string>( async ( url ) => await Browser.OpenAsync( url ) );

... and then tried the following -- again, no luck ...

public IRelayCommand OpenUrlCommand => new RelayCommand<string>( async ( url ) => await Launcher.OpenAsync( url ) );

I then replaced the call to open the url with a Debug.WriteLine( url ), and it seems that the OpenUrlCommand is never being called. Also, one would expect the cursor to change when hovering over the "link" text, but it never does -- it is as though the TapGesterRecognizer is not being created or registered.

As a last ditch effort, I also tried the following (again, copying from the docs):

<Label TextType="Html">
    <![CDATA[
       This is <a href="https://learn.microsoft.com/dotnet/maui/" target="_blank">a link to another site</a>.
    ]]>
 </Label>

... no luck -- the link appears underlined, but when clicked on, nothing happens (cursor does not change, browser does not open).

Any advice, or even better, a working example would be appreciated.

UPDATE: Found this post as well: https://github.com/dotnet/maui/issues/4734 - which looks like this was a known bug back in February!

As per @Jessie Zhang -MSFT, I ended up doing the following - which underlines the link, but with the caveat that the WHOLE label is tapable ...

     <!-- 
     
     This works - HOWEVER, the WHOLE label is tappable.  We cannot use
     Span.GestureRecognizers because of the following issue:
     
     https://github.com/dotnet/maui/issues/4734
     
     -->

     <Label>
        <Label.GestureRecognizers>
           <TapGestureRecognizer 
              Command="{Binding OpenUrlCommand}"
              CommandParameter="https://learn.microsoft.com/en-us/dotnet/maui/" 
           />
        </Label.GestureRecognizers>
        <Label.FormattedText>
           <FormattedString>
              <Span 
                 Text="To learn more, " 
              />
              <Span 
                 Text="check the documentation"
                 TextColor="Blue"
                 TextDecorations="Underline">
              </Span>
              <Span 
                 Text="." 
              />
           </FormattedString>
        </Label.FormattedText>
     </Label>

... and in my ViewModel:

  public IRelayCommand OpenUrlCommand => new RelayCommand<String>( launch_browser );


  private async void launch_browser( String url )
  {

     Debug.WriteLine( $"*** Tap: {url}" );

     await Browser.OpenAsync( url );

  }

... the above works (for me) in the Windows and Andriod simulators.

Tauromachy answered 12/9, 2022 at 22:43 Comment(5)
Why would expect the cursor to change? it is just a TapGestureRecognizer?Winnifredwinning
have you checked the docs about data binding/command it might help: learn.microsoft.com/en-us/dotnet/maui/fundamentals/data-binding/… ?Winnifredwinning
I would expect (on the Windows) emulator when using a mouse, the cursor would change when hovering over a hyperlink as it does with most Windows applications. Yes - have read the data binding fundamentals -- I literally copied and pasted the code from the docs and changed the command name. If you have a working example, please share. Thanks!Tauromachy
is the BindingContext set properly?Winnifredwinning
Yes -- intellisense displays the OpenUrlCommand and the View sets the BindingContext to the ViewModel.Tauromachy
B
8

Yes, it is a known issue about this problem.

You can follow it here: https://github.com/dotnet/maui/issues/4734 .

But as a workaround, you can use Label.GestureRecognizers instead of Span.GestureRecognizers.

For example:

       <Label 
        Text="click here"
        VerticalOptions="Center" 
        HorizontalOptions="Center" >

        <Label.GestureRecognizers>
            <TapGestureRecognizer Command="{Binding TapCommand}"
                                      CommandParameter="https://learn.microsoft.com/dotnet/maui/" />
        </Label.GestureRecognizers>

    </Label>
Bickerstaff answered 13/9, 2022 at 2:13 Comment(3)
Thanks for confirming - I actually found the issue (4734) and added an update to my OP. Any ETA on when this will be fixed?Tauromachy
We apologize for the inconvenience, but we need to wait patiently for the release of the new version.Bickerstaff
Marked as answered and updated my OP with updated code.Tauromachy
B
4

This is my solution - I had a look at this section of the Microsoft docs which adds functionality to a Span but it made more sense for me to extend the Label control in the same way:

namespace MyApp.Controls
{
    public class HyperlinkLabel : Label
    {
        public static readonly BindableProperty UrlProperty =
            BindableProperty.Create(nameof(Url), typeof(string), typeof(HyperlinkLabel), null);

        public string Url
        {
            get { return (string)GetValue(UrlProperty); }
            set { SetValue(UrlProperty, value); }
        }

        public HyperlinkLabel()
        {
            TextDecorations = TextDecorations.Underline;
            TextColor = Colors.Blue;
            GestureRecognizers.Add(new TapGestureRecognizer
            {
                // Launcher.OpenAsync is provided by Essentials.
                Command = new Command(async () => await Launcher.OpenAsync(Url))
            });
        }
    }
}

Then in XAML:

xmlns:controls="clr-namespace:MyApp.Controls"

...

<controls:HyperlinkLabel
    Text="Click me"
    Url="https://learn.microsoft.com/dotnet/" />

I'm new to XAML and MAUI so would be happy for any correction/input.

Buonomo answered 10/11, 2022 at 4:32 Comment(0)
Z
1

I figured out how to do this in xamarin with an android renderer by setting the native textview' AutoLinkMask property to All. I didn't need to do this for ios because it already seemed to work.

textView.AutoLinkMask = MatchOptions.All;

In maui we can do similar. This will turn this on for ALL labels in the application

#if ANDROID
                Microsoft.Maui.Handlers.LabelHandler.Mapper.AppendToMapping("AutoLinkMask", (h,e) => {
                    h.PlatformView.AutoLinkMask = Android.Text.Util.MatchOptions.All;
                });
#endif

I put this code in my MauiProgram.cs file, but apparently it can work in other locations.

Now if the text set to the label contains a properly formatted url, the url will become a link, and clicking on the link will open the browser on the device. No extra click handling required!

Edit: Seems this approach is not without its issues. I had a label with a link in a list's item template and opened the link instead of clicking the item. The link is also not rendering consistently in my collectionView of my chat messages. Hoping this approach may help inspire someone with a better idea though. Best of luck!

Zeb answered 24/4, 2023 at 20:44 Comment(1)
It does the Job in MAUI but It also converts many numbers and the words having DOT . in between, etc everything becomes link. That's not anybody would wantOzzie
U
1

Finally this issue has been resolved in .NET MAUI in .NET 8 Preview 5

... Bug Fixes: Several bug fixes were implemented, addressing issues such as gestures in Label Spans, Entry issues with the keyboard, Label truncation on iOS, CollectionView issues, ContentView RTL, and more. #14410, #14382, #14453, #14391, #11763, #15114, #12909

https://devblogs.microsoft.com/dotnet/announcing-dotnet-maui-in-dotnet-8-preview-5/

Utter answered 16/6, 2023 at 22:52 Comment(0)
G
0

This is/was a problem in Xamarin.Forms as well, and I suspect the underlying issue is the same. I came across the issue a couple of years ago when implementing a custom chatbot.

The problem is caused by Label being a single UI object and how FormattedText and the associated spans are implemented in the Label renderers (I’m assuming the .Net MAUI Handlers have the same issue). The problem is that the Label is a single UI object and there is no two dimensional position information calculated and associated with a span, so there is no way to associate a gesture recognizer with either a separate UI object for the span or directly with coordinates on the screen. The Xamarin.Forms implementation uses the latter approach and tries to calculate the screen coordinates of the span, however it’s an estimation process which is flawed, usually resulting in the actual active tap area being incorrect and inconsistent. The only way I got something working consistently was to place the link text in a separate label and associate the gesture recognizer with that (as has been suggested in a previous answer). In our chatbot use case, this meant splitting messages around links and displaying all links on their own line(s) - they cant be true in-line links. This work around was acceptable to the client.

This is a fundamentally difficult problem to solve, given the way that the Label renderers/handlers have been implemented. If you must have true in-line links, it may be best to create a new type of Label and create custom renderers/handlers for it that it utilize the way the use case is handled on each platform. On Android this might entail using TextView, mapping the text to HTML and setting the MovementMethod (https://learntodroid.com/how-to-create-a-hyperlink-using-android-textview/), and on iOS using UITextView with NSAttributedStrings, or subclassing UILabel and creating a custom implementation (https://augmentedcode.io/2020/12/20/opening-hyperlinks-in-uilabel-on-ios/). Another approach might be to use an embedded WebView and pass it the content in html format, maybe even embedding javascript in it depending on the use case.

Obviously, if you can accept having your links on a separate line rather than be truly in-line, then using a separate Label object will be far easier.

Gynecoid answered 8/4, 2023 at 23:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.