眠いしお腹すいたし(´・ω・`)

C#関連を主に書きます。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

Xamarin.FormsでLink表現してみた( ̄ー+ ̄)

今回はXamarin.FormsでLink表現をおこなってみました。

LinkerLabelって名前にしています。

完成動画はこち

https://sleepyandhungry1984.tumblr.com/post/162190761683/links-in-label-for-xamarinforms
sleepyandhungry1984.tumblr.com

コードはこち

github.com

スマホアプリでリンク表現が正しいのか・・・

..(/^^)/ ソレハコッチニオイトイテ

表現手段が増えることは良い事です。

利用方法

まずは、このLinkerLabelをどのように使うかのコードを示します。

          <shared:LinkerLabel Text="{Binding BaseText}" 
                              FontSize="15"
                              VerticalOptions="Center" HorizontalTextAlignment="Start"
                              HorizontalOptions="Center"  ItemsSource="{Binding LinkWords}" Command="{Binding LinkCommand}"/>

LinkerLabelはLabelを継承していますので基本的にはLabelです。

Labelとの違いはItemsSourceとCommandのBindablePropertyを持っている事です。

ItemsSourceにIEnumerableを渡してあげると、Text文字列中で、その配列に含まれる文字と一致する部分をLink表現にします。 そしてCommandを設定しておくとLink部分をタップした際にコマンドが実行されコマンドプロパティとしてタップした文字が渡されます。

ItemsSourceは当然INotifyCollectionChangedに対応していますのでItemsSourceを変更すればLink表現も追従します。

今回のサンプルのViewModelはLinkerLabel/MainPageViewModel.cs at master · yuka1984/LinkerLabel · GitHubですが画面から入力した文字列をLinkWordsコレクションに追加する事でLink表現が変化しています。

では実装方法を見ていきましょう。

共通実装部分

LinkerLabel/LinkerLabel.cs at master · yuka1984/LinkerLabel · GitHub

Labelを継承してItemsSourceやCommand、LinkColorのプロパティを増やしてそれに対する実装をおこなっています。

ItemsSourceが変更された際には

        private void UpdateMatchWords()
        {
            var txt = Text;
            var buffer = new List<MatchWord>();
            var sources = ItemsSource.Cast<string>().ToList();
            foreach (var source in sources)
            {
                var matches = Regex.Matches(txt, source);
                if (matches.Count > 0)
                    foreach (Match match in matches)
                        if (!buffer.Any(x => x.StartPosition <= match.Index && x.EndPositon >= match.Index))
                            buffer.Add(new MatchWord
                            {
                                Word = source,
                                StartPosition = match.Index
                            });
            }
            _matchWords = buffer;
            OnPropertyChanged(nameof(MatchWords));
        }

こんな感じでTextからLink箇所を抽出して配列化してinternalなプロパティMatchWordsを設定しています。

Rendererの方でMatchWordsの変更を受け取ってプラットフォーム毎にゴニョゴニョする感じになります。

Androidの実装

コードはこち

LinkerLabel/LinkerLabelRenderer.cs at master · yuka1984/LinkerLabel · GitHub

qiita.com

ほぼ記事そのままの実装なので詳細はこちらの記事を見てください。

要約するとSpannableStringというクラスを使うことでリンク表現できますよ~ってことです。

iOSの実装

コードはこち

LinkerLabel/LinkerLabelRenderer.cs at master · yuka1984/LinkerLabel · GitHub

qiita.com

ほぼ記事そのままの実装なので詳細はこちらの記事を見てください。

要約すると

NSMutableAttributedStringというクラスを使うことでリンク表現できますよ~

Tap検出はUITextView.GetClosestPositionToPointできますよ~

でもLabelのiOSでのプラットフォームコントロールはUILabelなのでできませんよ~

なので仮想のUITextViewを作ってtap検出しましたよ~

っていう感じです。

Xamarin iOSでNSLocationInRange関数が見つからなくて代替手段を考えるのに少し時間がかかりました。

おわりに

今回はしませんでしたがURL検出などを行うようにすればAndroidのLinkifyみたいなこともXamarin.Formsでも可能かもしれませんね。

最近はXamarin Androidでの開発の勉強をしていてDroidKaigi2017アプリをXamarin Androidで書くとどうなるか?みたいな研究をしているのですが、これが本当に難しくて今まで私がXamarin Nativeに思っていた事と現実にはかなりの差があって、考えを改めながら色々と作っている最中です。

ずっとローカルで作業していたのですが最近GitHub上に公開しました。

GitHub - yuka1984/DroidKaigi2017forXamarin

まだまだなのですが、少しずつXamarin Androidでの開発を習得していきたいと思います。

それではまた~(^^)/