Xamarin.FormsでURLをリンクにする

Xamarin.Forms の Label はただ文字列を表示するしか能がありません。

文字列に含まれるURLを自動でリンク表示させてみます。

追加パッケージとか無しでこれだけでOKです。

## 実装

### PCLコード

リンクの文字色をXAMLから変更できるように、バインド可能プロパティを追加しておきます。

```csharp:LinkLabel.cs
using Xamarin.Forms;

namespace LinkSample
{
    public class LinkLabel : Label
    {
        public static readonly BindableProperty LinkTextColorProperty = BindableProperty.Create(
            "LinkTextColor", typeof(Color), typeof(LinkLabel), Color.Default
        );

        public Color LinkTextColor {
            get { return (Color)GetValue(LinkTextColorProperty); }
            set { SetValue(LinkTextColorProperty, value); }
        }
    }
}
```

### カスタムレンダラー

#### iOS
ちょっと長い。

```csharp:LinkLabelRenderer.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text.RegularExpressions;

using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

using Foundation;
using UIKit;

using LinkSample;
using LinkSample.iOS;

[assembly: ExportRenderer(typeof(LinkLabel), typeof(LinkLabelRenderer))]

namespace LinkSample.iOS
{
    public class LinkLabelRenderer : LabelRenderer
    {
        readonly List<Match> Matches = new List<Match>();

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

            Control.UserInteractionEnabled = true;
            Control.AddGestureRecognizer(new UITapGestureRecognizer(this.OnTapUrl));

            UpdateLinks();
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);

            if (e.PropertyName == nameof(this.Element.Text)) {
                UpdateLinks();
            }
        }

        protected void UpdateLinks()
        {
            var element = Element as LinkLabel;

            var attributedString = new NSMutableAttributedString(Element.Text, Control.Font, Element.TextColor.ToUIColor());

            attributedString.AddAttribute(
                UIStringAttributeKey.ForegroundColor,
                Element.TextColor.Equals(Color.Default) ? Color.Black.ToUIColor() : Element.TextColor.ToUIColor(),
                new NSRange(0, Element.Text.Length)
            );

            var matches = Regex.Matches(Element.Text, @"https?://[-_.!~*'a-zA-Z0-9;/?:@&=+$,%#]+");

            foreach (Match match in matches) {
                var range = new NSRange(match.Index, match.Length);
                attributedString.AddAttribute(UIStringAttributeKey.ForegroundColor, element.LinkTextColor.ToUIColor(), range);
                this.Matches.Add(match);
            }

            Control.AttributedText = attributedString;
        }

        protected void OnTapUrl(UIGestureRecognizer tap)
        {
            var location = tap.LocationInView(Control);
            var textView = new UITextView(Control.Frame);

            textView.TextContainer.LineFragmentPadding = 0;
            textView.TextContainerInset = UIEdgeInsets.Zero;
            textView.AttributedText = Control.AttributedText;

            var position = textView.GetOffsetFromPosition(
                textView.BeginningOfDocument,
                textView.GetClosestPositionToPoint(location)
            );

            var url = this.Matches.FirstOrDefault(m =>
                m.Index <= position && position <= (m.Index + m.Length)
            );

            if (url != null) {
                UIApplication.SharedApplication.OpenUrl(new Uri(url.Value));
            }
        }
    }
}
```
### Android
めずらしくAndroidが簡単です。

```csharp:LinkLabelRenderer.cs
using System;

using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

using Android.Text.Util;

using LinkSample;
using LinkSample.Droid;

[assembly: ExportRenderer(typeof(LinkLabel), typeof(LinkLabelRenderer))]

namespace LinkSample.Droid
{
    public class LinkLabelRenderer : LabelRenderer
    {
        public LinkLabelRenderer(Android.Content.Context context) : base(context) {}

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

            var element = Element as LinkLabel;

            Linkify.AddLinks(this.Control, MatchOptions.WebUrls);
            this.Control.SetLinkTextColor(element.LinkTextColor.ToAndroid());
        }
    }
}
```
### XAML
```xml:XAML
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:LinkSample"
    x:Class="LinkSample.LinkSamplePage">
    
	<local:LinkLabel
        Text="Xamarin(https://www.xamarin.com) on Qiita(https://qiita.com) LinkSample."
        TextColor="Gray"
        LinkTextColor="Green"
        FontSize="22"
        VerticalOptions="Center"
        HorizontalOptions="Center"
        HorizontalTextAlignment="Center"
        Margin="15" />
    
</ContentPage>
```


## 結果

### iOS
![result_ios_1.png](https://qiita-image-store.s3.amazonaws.com/0/169798/af417f77-6061-9789-2596-8fc4f7342e49.png) ![result_ios_2.png](https://qiita-image-store.s3.amazonaws.com/0/169798/dc775fba-4d47-8dc2-1edb-3015e4ed910f.png)


### Android
![result_android_1.png](https://qiita-image-store.s3.amazonaws.com/0/169798/3f5b4c3b-b434-7c32-528c-ffd12f89da7a.png) ![result_android_2.png](https://qiita-image-store.s3.amazonaws.com/0/169798/b3aa3451-c9b1-ce3e-486f-faebac8b0b03.png)

URLが勝手にリンクになって、タップするとブラウザで開けました。

とりあえずは以上です。

※ AndroidでURLタップした時の背景色はどうやって指定するのだろうか...

## こちらを参考にしました
http://tamafuyou.hatenablog.com/entry/Xamarin_Forms_Label_Link