diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/HtmlTextLinkShouldBeClickable.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/HtmlTextLinkShouldBeClickable.png
new file mode 100644
index 000000000000..f1fa4458add6
Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/HtmlTextLinkShouldBeClickable.png differ
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issues21328.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issues21328.xaml
new file mode 100644
index 000000000000..3a387d7f55ce
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issues21328.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issues21328.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issues21328.xaml.cs
new file mode 100644
index 000000000000..7bc5da936f4c
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issues21328.xaml.cs
@@ -0,0 +1,16 @@
+namespace Controls.TestCases.HostApp.Issues;
+
+[Issue(IssueTracker.Github, 21328, "Link in Label with TextType HTML is not clickable", PlatformAffected.iOS | PlatformAffected.Android)]
+public partial class Issues21328 : ContentPage
+{
+ public Issues21328()
+ {
+ InitializeComponent();
+ label.Text = @"Example HTML link";
+ label2.Text = @"Normal Text and Example HTML link here";
+ }
+ private void Button_Clicked(object sender, EventArgs e)
+ {
+ label2.Text = @"Visit dotnet from here ";
+ }
+}
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/HtmlTextLinkShouldBeClickable.png b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/HtmlTextLinkShouldBeClickable.png
new file mode 100644
index 000000000000..9f7dc0709fed
Binary files /dev/null and b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/HtmlTextLinkShouldBeClickable.png differ
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue21328.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue21328.cs
new file mode 100644
index 000000000000..5fc7cd0321db
--- /dev/null
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue21328.cs
@@ -0,0 +1,28 @@
+using System;
+using NUnit.Framework;
+using UITest.Appium;
+using UITest.Core;
+
+namespace Microsoft.Maui.TestCases.Tests.Tests.Issues;
+
+public class Issue21328 : _IssuesUITest
+{
+ public Issue21328(TestDevice device) : base(device)
+ {
+ }
+
+ public override string Issue => "Link in Label with TextType HTML is not clickable";
+
+#if !WINDOWS
+
+ [Test]
+ [Category(UITestCategories.Label)]
+ public void HtmlTextLinkShouldBeClickable()
+ {
+ App.WaitForElement("HtmlLinkLabel");
+ App.Tap("HtmlLinkLabel");
+ Thread.Sleep(3000);
+ VerifyScreenshot();
+ }
+#endif
+}
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/HtmlTextLinkShouldBeClickable.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/HtmlTextLinkShouldBeClickable.png
new file mode 100644
index 000000000000..cb8764e81cef
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/HtmlTextLinkShouldBeClickable.png differ
diff --git a/src/Core/src/Platform/Android/TextViewExtensions.cs b/src/Core/src/Platform/Android/TextViewExtensions.cs
index 6b93fd9c17aa..cd4152d56484 100644
--- a/src/Core/src/Platform/Android/TextViewExtensions.cs
+++ b/src/Core/src/Platform/Android/TextViewExtensions.cs
@@ -2,6 +2,7 @@
using System.Net;
using Android.Graphics;
using Android.Text;
+using Android.Text.Method;
using Android.Widget;
using static Android.Widget.TextView;
using ALayoutDirection = Android.Views.LayoutDirection;
@@ -18,6 +19,9 @@ public static void UpdateTextPlainText(this TextView textView, IText label)
public static void UpdateTextHtml(this TextView textView, ILabel label)
{
+ if(textView.MovementMethod is null)
+ textView.MovementMethod = LinkMovementMethod.Instance ?? new LinkMovementMethod();
+
var text = label.Text ?? string.Empty;
var htmlText = WebUtility.HtmlDecode(text);
diff --git a/src/Core/src/Platform/iOS/LabelExtensions.cs b/src/Core/src/Platform/iOS/LabelExtensions.cs
index b853b859540f..0bb966c08df4 100644
--- a/src/Core/src/Platform/iOS/LabelExtensions.cs
+++ b/src/Core/src/Platform/iOS/LabelExtensions.cs
@@ -1,3 +1,4 @@
+using System;
using Foundation;
using Microsoft.Maui.Graphics;
using ObjCRuntime;
@@ -91,6 +92,56 @@ internal static void UpdateTextHtml(this UILabel platformLabel, ILabel label)
#pragma warning disable CS8601
platformLabel.AttributedText = new NSAttributedString(text, attr, ref nsError);
#pragma warning restore CS8601
+ platformLabel.UserInteractionEnabled = true;
+ platformLabel.DetectAndOpenLink();
+ }
+ internal static void RemoveCurrentGesture(this UILabel platformLabel)
+ {
+ if(platformLabel.GestureRecognizers is null)
+ return;
+ foreach(var gesture in platformLabel.GestureRecognizers)
+ {
+ if(gesture is HtmlTextGestureRecognizer htmlTextGesture)
+ {
+ platformLabel.RemoveGestureRecognizer(htmlTextGesture);
+ break;
+ }
+ }
+ }
+
+ internal static void DetectAndOpenLink(this UILabel platformLabel)
+ {
+ platformLabel.RemoveCurrentGesture();
+ var tapGesture = new HtmlTextGestureRecognizer((UITapGestureRecognizer recognizer) =>
+ {
+ if (recognizer.State != UIGestureRecognizerState.Recognized) return;
+ if (platformLabel.AttributedText is null) return;
+
+ var layoutManager = new NSLayoutManager();
+ var textStorage = new NSTextStorage();
+ var textContainer = new NSTextContainer();
+
+ textStorage.SetString(platformLabel.AttributedText);
+ layoutManager.AddTextContainer(textContainer);
+ textStorage.AddLayoutManager(layoutManager);
+
+ textContainer.LineFragmentPadding = 0;
+ textContainer.LineBreakMode = platformLabel.LineBreakMode;
+ textContainer.Size = platformLabel.Bounds.Size;
+
+ var location = recognizer.LocationInView(platformLabel);
+ var index = (nint)layoutManager.GetCharacterIndex(location, textContainer);
+
+ if (index < platformLabel.AttributedText.Length)
+ {
+ var url = platformLabel.AttributedText.GetAttribute(UIStringAttributeKey.Link, index, out _);
+ if (url is NSUrl nsUrl)
+ {
+ UIApplication.SharedApplication.OpenUrl(nsUrl,new UIApplicationOpenUrlOptions(),null);
+ }
+ }
+ });
+ platformLabel.AddGestureRecognizer(tapGesture);
}
internal static void UpdateTextPlainText(this UILabel platformLabel, IText label)
@@ -98,4 +149,15 @@ internal static void UpdateTextPlainText(this UILabel platformLabel, IText label
platformLabel.Text = label.Text;
}
}
+
+ internal class HtmlTextGestureRecognizer: UITapGestureRecognizer
+ {
+ public HtmlTextGestureRecognizer(Action action):base(action)
+ {
+ CancelsTouchesInView = false;
+ ShouldRecognizeSimultaneously = GetRecognizeSimultaneously;
+ }
+
+ private bool GetRecognizeSimultaneously(UIGestureRecognizer gestureRecognizer, UIGestureRecognizer otherGestureRecognizer) => true;
+ }
}