Forum Xamarin Xamarin.Forms

How to dynamically add Gesture Recognizer to Text in Label?

OlivetreeOlivetree Member ✭✭

Hey guys!
I am trying to build something similar to a social network where I am using bindings to get text from viewmodel into my xaml view.
Some of these texts will have mentions using @someonesnickname and I would like to know if there is way to use a converter or something else to get this text and add a gesture recognizer so that when the user tap on the person's username it navigates to another view to check the other person's profile.
Is that possible?

One thing to keep in mind is that I would not know before hand what the text will be, so it may or may not have someone else's username in there, but when it has, it creates the span with the tap gesture recognizer.

Another thing to keep in mind, I am loading this inside a listview. AKA: there may be many posts and each post may or may not have mention to another user.

Thank you.

Best Answer

  • OlivetreeOlivetree Member ✭✭
    edited July 2019 Accepted Answer

    Edit
    I just found out I can post code YEY!

    Alright, I made it. I don't think I can post code in the formatted way, so I will try to make it at least look readible.

    The converter

    public class UsernameConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var formatted = new FormattedString();
    
            foreach (var item in ProcessString((string)value))
                formatted.Spans.Add(CreateSpan(item));
    
            return formatted;
        }
    
        private Span CreateSpan(StringSection section)
        {
            var span = new Span()
            {
                Text = section.Text
            };
    
            if (!string.IsNullOrEmpty(section.Username))
            {
                span.GestureRecognizers.Add(new TapGestureRecognizer()
                {
                    Command = _navigationCommand,
                    CommandParameter = section.Username
                });
                span.TextColor = (Color)Application.Current.Resources["PrimaryColor"];
                //span.TextDecorations = TextDecorations.Underline;
            }
    
            return span;
        }
    
        public IList<StringSection> ProcessString(string rawText)
        {
            const string spanPattern = @"@\w+";
    
            MatchCollection collection = Regex.Matches(rawText, spanPattern, RegexOptions.Singleline);
    
            var sections = new List<StringSection>();
    
            var lastIndex = 0;
    
            foreach (Match item in collection)
            {
                var foundText = item.Value;
                sections.Add(new StringSection() { Text = rawText.Substring(lastIndex, item.Index - lastIndex) });
                lastIndex += item.Index - lastIndex + item.Length;
    
                //Get the user
                var user = new StringSection()
                {
                    Username = Regex.Replace(item.Value, @"@", string.Empty),
                    Text = item.Value
                };
    
                sections.Add(user);
            }
    
            sections.Add(new StringSection() { Text = rawText.Substring(lastIndex) });
    
            return sections;
        }
    
        public class StringSection
        {
            public string Text { get; set; }
            public string Username { get; set; }
        }
    
        private ICommand _navigationCommand = new Command<string>((username) =>
        {
            PageService ps = new PageService();
            ps.PushAsync(new ProfilePage(username));
        });
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    

    =================================

    This guy creates a formatted label and breaks the text into spans... every time it finds the at symbol(@) it will create a new span with the word next to it. So if my text has many @s, it will create one for each user. Then it will also get the username to use later, when/if the username is clicked.
    Now, when it creates the spans, if it has a username(aka, if it contains an @ symbol) it will change the color to my "PrimaryColor" which I set in my app.xaml and add the _navigationCommand.
    The _navigationCommand will just navigate to the correct page sending the username as a parameter.

    Now, to consume it I have to:
    1 - Add the converter in the beginning of the page as part of the Resource Dictionary

    <ContentPage.Resources>
        <ResourceDictionary>
            <converters:UsernameConverter x:Key="UsernameConverter" />
        </ResourceDictionary>
    </ContentPage.Resources>
    

    2 - Consume it this way

    <Label FormattedText="{Binding Content, Converter={StaticResource UsernameConverter}}" />
    

    Now it displays as it should.

    I had to change a couple of things, especially the when it add sections and calculate the last Index to ensure it can have more than on mention in each post.!

    Thanks everyone.

Answers

  • LeonLuLeonLu Member, Xamarin Team Xamurai

    Do you want to add the Gesture for the Label in the listview? If so , you can see this GIF.

    I add the GestureRecognizers for the label, Here is my layout.

          <StackLayout>
        <ListView x:Name="EmployeeView"
           >
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout>
                            <Label Text="{Binding Name}" WidthRequest="150" HorizontalOptions="Start" TextDecorations="Underline">
                                <Label.GestureRecognizers>
                                    <TapGestureRecognizer
                                        Tapped="TapGestureRecognizer_Tapped" 
    
                                        />
                                </Label.GestureRecognizers>
                            </Label>
                            <Label Text="{Binding Age}" >
    
                            </Label>
    
                        </StackLayout>
    
                    </ViewCell>
    
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    
    </StackLayout>
    

    Background code,

       public MainPage()
        {
            InitializeComponent();
            ObservableCollection<Employee> employees = new ObservableCollection<Employee>();
            employees.Add(new Employee() {Name="[email protected]" ,Age="1"});
            employees.Add(new Employee() { Name = "[email protected]", Age = "1" });
            employees.Add(new Employee() { Name = "[email protected]l.com", Age = "1" });
            employees.Add(new Employee() { Name = "[email protected]", Age = "1" });
            employees.Add(new Employee() { Name = "[email protected]", Age = "1" });
            employees.Add(new Employee() { Name = "[email protected]", Age = "1" });
            employees.Add(new Employee() { Name = "[email protected]", Age = "1" });
            employees.Add(new Employee() { Name = "[email protected]", Age = "1" });
            employees.Add(new Employee() { Name = "[email protected]", Age = "1" });
            employees.Add(new Employee() { Name = "[email protected]", Age = "1" });
            employees.Add(new Employee() { Name = "[email protected]", Age = "1" });
            employees.Add(new Employee() { Name = "[email protected]", Age = "1" });
            employees.Add(new Employee() { Name = "[email protected]", Age = "1" });
            employees.Add(new Employee() { Name = "[email protected]", Age = "1" });
    
            EmployeeView.ItemsSource = employees;
       }
    
        private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
        {
            var myLable=(Label)sender;
    
            Navigation.PushModalAsync(new Page1(myLable.Text));
        }
    
  • OlivetreeOlivetree Member ✭✭
    edited July 2019

    Almost that. On this code, all the information is displayed the exact same way. So if I would like a text on top, for example, and half way through the text a tag... I wouldn't be able to do that.

    I am trying to achieve something more like this. This is just a picture of a random facebook post where people are tagging each other. What I am trying to do is to find these tags ("@\") and convert them into those "hyperlinks". Since I am not sure when the user is going to use them, I cannot have two labels.

    Now, I found this yesterday, it achieves the same thing I am trying to, but instead of using "@"", it uses the html "" tags. I will try to make some changes to this code and see if I can achieve the same thing using "@" and will let you guys know later :smiley:
    https://xamarinhelp.com/hyperlink-in-xamarin-forms-label/

  • LeonLuLeonLu Member, Xamarin Team Xamurai

    Ok, waitting for your update.

  • OlivetreeOlivetree Member ✭✭
    edited July 2019 Accepted Answer

    Edit
    I just found out I can post code YEY!

    Alright, I made it. I don't think I can post code in the formatted way, so I will try to make it at least look readible.

    The converter

    public class UsernameConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var formatted = new FormattedString();
    
            foreach (var item in ProcessString((string)value))
                formatted.Spans.Add(CreateSpan(item));
    
            return formatted;
        }
    
        private Span CreateSpan(StringSection section)
        {
            var span = new Span()
            {
                Text = section.Text
            };
    
            if (!string.IsNullOrEmpty(section.Username))
            {
                span.GestureRecognizers.Add(new TapGestureRecognizer()
                {
                    Command = _navigationCommand,
                    CommandParameter = section.Username
                });
                span.TextColor = (Color)Application.Current.Resources["PrimaryColor"];
                //span.TextDecorations = TextDecorations.Underline;
            }
    
            return span;
        }
    
        public IList<StringSection> ProcessString(string rawText)
        {
            const string spanPattern = @"@\w+";
    
            MatchCollection collection = Regex.Matches(rawText, spanPattern, RegexOptions.Singleline);
    
            var sections = new List<StringSection>();
    
            var lastIndex = 0;
    
            foreach (Match item in collection)
            {
                var foundText = item.Value;
                sections.Add(new StringSection() { Text = rawText.Substring(lastIndex, item.Index - lastIndex) });
                lastIndex += item.Index - lastIndex + item.Length;
    
                //Get the user
                var user = new StringSection()
                {
                    Username = Regex.Replace(item.Value, @"@", string.Empty),
                    Text = item.Value
                };
    
                sections.Add(user);
            }
    
            sections.Add(new StringSection() { Text = rawText.Substring(lastIndex) });
    
            return sections;
        }
    
        public class StringSection
        {
            public string Text { get; set; }
            public string Username { get; set; }
        }
    
        private ICommand _navigationCommand = new Command<string>((username) =>
        {
            PageService ps = new PageService();
            ps.PushAsync(new ProfilePage(username));
        });
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    

    =================================

    This guy creates a formatted label and breaks the text into spans... every time it finds the at symbol(@) it will create a new span with the word next to it. So if my text has many @s, it will create one for each user. Then it will also get the username to use later, when/if the username is clicked.
    Now, when it creates the spans, if it has a username(aka, if it contains an @ symbol) it will change the color to my "PrimaryColor" which I set in my app.xaml and add the _navigationCommand.
    The _navigationCommand will just navigate to the correct page sending the username as a parameter.

    Now, to consume it I have to:
    1 - Add the converter in the beginning of the page as part of the Resource Dictionary

    <ContentPage.Resources>
        <ResourceDictionary>
            <converters:UsernameConverter x:Key="UsernameConverter" />
        </ResourceDictionary>
    </ContentPage.Resources>
    

    2 - Consume it this way

    <Label FormattedText="{Binding Content, Converter={StaticResource UsernameConverter}}" />
    

    Now it displays as it should.

    I had to change a couple of things, especially the when it add sections and calculate the last Index to ensure it can have more than on mention in each post.!

    Thanks everyone.

Sign In or Register to comment.