Xamarin.Forms reads and displays Android and iOS contacts-TerminalMACS client

Original: Xamarin.Forms reads and displays Android and iOS contacts-TerminalMACS client

Xamarin.Forms reads and displays Android and iOS contacts-TerminalMACS client

This article updates the address synchronously:

Reading navigation:

  • 1. Function description
  • Second, the code implementation
  • Three, source code acquisition
  • 4. Reference materials
  • Five, the plan behind

1. Function description

Complete mind map: https://github.com/dotnet9/TerminalMACS/blob/master/docs/TerminalMACS.xmind
Xamarin.Forms client address book function

This article introduces the function in the red circle on the right side of the figure, that is, using Xamarin.Forms to obtain and display the address book information of Android and iOS. The following is the final effect. Since the real phone is used, the contact name and phone number are coded display.

Contact list

And the search function is simply processed. The reason why it is simple is that the address book list is all read out, and the search is directly filtered from this list.

The following picture comes from: https://www.xamboy.com/2019/10/10/getting-phone-contacts-in-xamarin-forms/, this function is written with reference to this article, so directly quote the pictures in the article.

Address book search list

Second, the code implementation

1. Create a contact entity class in the shared library project: Contacts.cs

namespace TerminalMACS.Clients.App.Models
{
    /// <summary>
    /// 通讯录
    /// </summary>
    public class Contact
    {
        /// <summary>
        /// 获取或者设置名称
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 获取或者设置 头像
        /// </summary>
        public string Image { get; set; }
        /// <summary>
        /// 获取或者设置 邮箱地址
        /// </summary>
        public string[] Emails { get; set; }
        /// <summary>
        /// 获取或者设置 手机号码
        /// </summary>
        public string[] PhoneNumbers { get; set; }
    }
}

2. The shared library creates the address book service interface: IContactsService.cs

include:

  • An address book acquisition request interface: RetrieveContactsAsync
  • One notification event for reading a communication result: OnContactLoaded
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using TerminalMACS.Clients.App.Models;

namespace TerminalMACS.Clients.App.Services
{
    /// <summary>
    /// 通讯录事件参数
    /// </summary>
    public class ContactEventArgs:EventArgs
    {
        public Contact Contact { get; }
        public ContactEventArgs(Contact contact)
        {
            Contact = contact;
        }
    }

    /// <summary>
    /// 通讯录服务接口,android和iOS终端具体的通讯录获取服务需要继承此接口
    /// </summary>
    public interface IContactsService
    {
        /// <summary>
        /// 读取一条数据通知
        /// </summary>
        event EventHandler<ContactEventArgs> OnContactLoaded;
        /// <summary>
        /// 是否正在加载
        /// </summary>
        bool IsLoading { get; }
        /// <summary>
        /// 尝试获取所有通讯录
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        Task<IList<Contact>> RetrieveContactsAsync(CancellationToken? token = null);
    }
}

3. Add the address book service to the iOS project to implement the IContactsService interface:

using Contacts;
using Foundation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using TerminalMACS.Clients.App.Models;
using TerminalMACS.Clients.App.Services;

namespace TerminalMACS.Clients.App.iOS.Services
{
    /// <summary>
    /// 通讯录获取服务
    /// </summary>
    public class ContactsService : NSObject, IContactsService
    {
        const string ThumbnailPrefix = "thumb";

        bool requestStop = false;

        public event EventHandler<ContactEventArgs> OnContactLoaded;

        bool _isLoading = false;
        public bool IsLoading => _isLoading;

        /// <summary>
        /// 异步请求权限
        /// </summary>
        /// <returns></returns>
        public async Task<bool> RequestPermissionAsync()
        {
            var status = CNContactStore.GetAuthorizationStatus(CNEntityType.Contacts);

            Tuple<bool, NSError> authotization = new Tuple<bool, NSError>(status == CNAuthorizationStatus.Authorized, null);

            if (status == CNAuthorizationStatus.NotDetermined)
            {
                using (var store = new CNContactStore())
                {
                    authotization = await store.RequestAccessAsync(CNEntityType.Contacts);
                }
            }
            return authotization.Item1;

        }

        /// <summary>
        /// 异步请求通讯录,此方法由界面真正调用
        /// </summary>
        /// <param name="cancelToken"></param>
        /// <returns></returns>
        public async Task<IList<Contact>> RetrieveContactsAsync(CancellationToken? cancelToken = null)
        {
            requestStop = false;

            if (!cancelToken.HasValue)
                cancelToken = CancellationToken.None;

            // 我们创建了一个十进制的TaskCompletionSource
            var taskCompletionSource = new TaskCompletionSource<IList<Contact>>();

            // 在cancellationToken中注册lambda
            cancelToken.Value.Register(() =>
            {
                // 我们收到一条取消消息,取消TaskCompletionSource.Task
                requestStop = true;
                taskCompletionSource.TrySetCanceled();
            });

            _isLoading = true;

            var task = LoadContactsAsync();

            // 等待两个任务中的第一个任务完成
            var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);
            _isLoading = false;

            return await completedTask;

        }

        /// <summary>
        /// 异步加载通讯录,具体的通讯录读取方法
        /// </summary>
        /// <returns></returns>
        async Task<IList<Contact>> LoadContactsAsync()
        {
            IList<Contact> contacts = new List<Contact>();
            var hasPermission = await RequestPermissionAsync();
            if (hasPermission)
            {

                NSError error = null;
                var keysToFetch = new[] { CNContactKey.PhoneNumbers, CNContactKey.GivenName, CNContactKey.FamilyName, CNContactKey.EmailAddresses, CNContactKey.ImageDataAvailable, CNContactKey.ThumbnailImageData };

                var request = new CNContactFetchRequest(keysToFetch: keysToFetch);
                request.SortOrder = CNContactSortOrder.GivenName;

                using (var store = new CNContactStore())
                {
                    var result = store.EnumerateContacts(request, out error, new CNContactStoreListContactsHandler((CNContact c, ref bool stop) =>
                    {

                        string path = null;
                        if (c.ImageDataAvailable)
                        {
                            path = path = Path.Combine(Path.GetTempPath(), $"{ThumbnailPrefix}-{Guid.NewGuid()}");

                            if (!File.Exists(path))
                            {
                                var imageData = c.ThumbnailImageData;
                                imageData?.Save(path, true);


                            }
                        }

                        var contact = new Contact()
                        {
                            Name = string.IsNullOrEmpty(c.FamilyName) ? c.GivenName : $"{c.GivenName} {c.FamilyName}",
                            Image = path,
                            PhoneNumbers = c.PhoneNumbers?.Select(p => p?.Value?.StringValue).ToArray(),
                            Emails = c.EmailAddresses?.Select(p => p?.Value?.ToString()).ToArray(),

                        };

                        if (!string.IsNullOrWhiteSpace(contact.Name))
                        {
                            OnContactLoaded?.Invoke(this, new ContactEventArgs(contact));

                            contacts.Add(contact);
                        }

                        stop = requestStop;

                    }));
                }
            }

            return contacts;
        }


    }
}

4. Instructions for adding address book permission in Info.plist file in iOS project

Info.plist

5. Add the permission to read the address book in the Android project: AndroidManifest.xml

<uses-permission android:name="android.permission.READ_CONTACTS"/>

The full permissions are configured as follows

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.terminalmacs.clients.app">
	<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
	<application android:label="TerminalMACS.Clients.App.Android"></application>
	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.READ_CONTACTS"/>
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>

6. Add the address book service in the Android project to implement the IContactServer interface: ContactsService.cs

using Acr.UserDialogs;
using Android;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Database;
using Android.Provider;
using Android.Runtime;
using Android.Support.V4.App;
using Plugin.CurrentActivity;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using TerminalMACS.Clients.App.Models;
using TerminalMACS.Clients.App.Services;

namespace TerminalMACS.Clients.App.Droid.Services
{
    /// <summary>
    /// 通讯录获取服务
    /// </summary>
    public class ContactsService : IContactsService
    {
        const string ThumbnailPrefix = "thumb";
        bool stopLoad = false;
        static TaskCompletionSource<bool> contactPermissionTcs;
        public string TAG
        {
            get
            {
                return "MainActivity";
            }
        }
        bool _isLoading = false;
        public bool IsLoading => _isLoading;
        //权限请求状态码
        public const int RequestContacts = 1239;
        /// <summary>
        /// 获取通讯录需要的请求权限
        /// </summary>
        static string[] PermissionsContact = {
            Manifest.Permission.ReadContacts
        };

        public event EventHandler<ContactEventArgs> OnContactLoaded;
        /// <summary>
        /// 异步请求通讯录权限
        /// </summary>
        async void RequestContactsPermissions()
        {
            //检查是否可以弹出申请读、写通讯录权限
            if (ActivityCompat.ShouldShowRequestPermissionRationale(CrossCurrentActivity.Current.Activity, Manifest.Permission.ReadContacts)
                || ActivityCompat.ShouldShowRequestPermissionRationale(CrossCurrentActivity.Current.Activity, Manifest.Permission.WriteContacts))
            {
                // 如果未授予许可,请向用户提供其他理由用户将从使用权限的附加上下文中受益。
                // 例如,如果请求先前被拒绝。
                await UserDialogs.Instance.AlertAsync("通讯录权限", "此操作需要“通讯录”权限", "确定");
            }
            else
            {
                // 尚未授予通讯录权限。直接请求这些权限。
                ActivityCompat.RequestPermissions(CrossCurrentActivity.Current.Activity, PermissionsContact, RequestContacts);
            }
        }

        /// <summary>
        /// 收到用户响应请求权限操作后的结果
        /// </summary>
        /// <param name="requestCode"></param>
        /// <param name="permissions"></param>
        /// <param name="grantResults"></param>
        public static void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
        {
            if (requestCode == RequestContacts)
            {
                // 我们请求了多个通讯录权限,因此需要检查相关的所有权限
                if (PermissionUtil.VerifyPermissions(grantResults))
                {
                    // 已授予所有必需的权限,显示联系人片段。
                    contactPermissionTcs.TrySetResult(true);
                }
                else
                {
                    contactPermissionTcs.TrySetResult(false);
                }

            }
        }

        /// <summary>
        /// 异步请求权限
        /// </summary>
        /// <returns></returns>
        public async Task<bool> RequestPermissionAsync()
        {
            contactPermissionTcs = new TaskCompletionSource<bool>();

            // 验证是否已授予所有必需的通讯录权限。
            if (Android.Support.V4.Content.ContextCompat.CheckSelfPermission(CrossCurrentActivity.Current.Activity, Manifest.Permission.ReadContacts) != (int)Permission.Granted
                || Android.Support.V4.Content.ContextCompat.CheckSelfPermission(CrossCurrentActivity.Current.Activity, Manifest.Permission.WriteContacts) != (int)Permission.Granted)
            {
                // 尚未授予通讯录权限。
                RequestContactsPermissions();
            }
            else
            {
                // 已授予通讯录权限。
                contactPermissionTcs.TrySetResult(true);
            }

            return await contactPermissionTcs.Task;
        }

        /// <summary>
        /// 异步请求通讯录,此方法由界面真正调用
        /// </summary>
        /// <param name="cancelToken"></param>
        /// <returns></returns>
        public async Task<IList<Contact>> RetrieveContactsAsync(CancellationToken? cancelToken = null)
        {
            stopLoad = false;

            if (!cancelToken.HasValue)
                cancelToken = CancellationToken.None;

            // 我们创建了一个十进制的TaskCompletionSource
            var taskCompletionSource = new TaskCompletionSource<IList<Contact>>();

            // 在cancellationToken中注册lambda
            cancelToken.Value.Register(() =>
            {
                // 我们收到一条取消消息,取消TaskCompletionSource.Task
                stopLoad = true;
                taskCompletionSource.TrySetCanceled();
            });

            _isLoading = true;

            var task = LoadContactsAsync();

            // 等待两个任务中的第一个任务完成
            var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);
            _isLoading = false;

            return await completedTask;
        }

        /// <summary>
        /// 异步加载通讯录,具体的通讯录读取方法
        /// </summary>
        /// <returns></returns>
        async Task<IList<Contact>> LoadContactsAsync()
        {
            IList<Contact> contacts = new List<Contact>();
            var hasPermission = await RequestPermissionAsync();
            if (!hasPermission)
            {
                return contacts;
            }

            var uri = ContactsContract.Contacts.ContentUri;
            var ctx = Application.Context;
            await Task.Run(() =>
            {
                // 暂时只请求通讯录Id、DisplayName、PhotoThumbnailUri,可以扩展
                var cursor = ctx.ApplicationContext.ContentResolver.Query(uri, new string[]
                {
                        ContactsContract.Contacts.InterfaceConsts.Id,
                        ContactsContract.Contacts.InterfaceConsts.DisplayName,
                        ContactsContract.Contacts.InterfaceConsts.PhotoThumbnailUri
                }, null, null, $"{ContactsContract.Contacts.InterfaceConsts.DisplayName} ASC");
                if (cursor.Count > 0)
                {
                    while (cursor.MoveToNext())
                    {
                        var contact = CreateContact(cursor, ctx);

                        if (!string.IsNullOrWhiteSpace(contact.Name))
                        {
                            // 读取出一条,即通知界面展示
                            OnContactLoaded?.Invoke(this, new ContactEventArgs(contact));
                            contacts.Add(contact);
                        }

                        if (stopLoad)
                            break;
                    }
                }
            });

            return contacts;

        }

        /// <summary>
        /// 读取一条通讯录数据
        /// </summary>
        /// <param name="cursor"></param>
        /// <param name="ctx"></param>
        /// <returns></returns>
        Contact CreateContact(ICursor cursor, Context ctx)
        {
            var contactId = GetString(cursor, ContactsContract.Contacts.InterfaceConsts.Id);

            var numbers = GetNumbers(ctx, contactId);
            var emails = GetEmails(ctx, contactId);

            var uri = GetString(cursor, ContactsContract.Contacts.InterfaceConsts.PhotoThumbnailUri);
            string path = null;
            if (!string.IsNullOrEmpty(uri))
            {
                try
                {
                    using (var stream = Android.App.Application.Context.ContentResolver.OpenInputStream(Android.Net.Uri.Parse(uri)))
                    {
                        path = Path.Combine(Path.GetTempPath(), $"{ThumbnailPrefix}-{Guid.NewGuid()}");
                        using (var fstream = new FileStream(path, FileMode.Create))
                        {
                            stream.CopyTo(fstream);
                            fstream.Close();
                        }

                        stream.Close();
                    }


                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine(ex);
                }

            }
            var contact = new Contact
            {
                Name = GetString(cursor, ContactsContract.Contacts.InterfaceConsts.DisplayName),
                Emails = emails,
                Image = path,
                PhoneNumbers = numbers,
            };

            return contact;
        }

        /// <summary>
        /// 读取联系人电话号码
        /// </summary>
        /// <param name="ctx"></param>
        /// <param name="contactId"></param>
        /// <returns></returns>
        string[] GetNumbers(Context ctx, string contactId)
        {
            var key = ContactsContract.CommonDataKinds.Phone.Number;

            var cursor = ctx.ApplicationContext.ContentResolver.Query(
                ContactsContract.CommonDataKinds.Phone.ContentUri,
                null,
                ContactsContract.CommonDataKinds.Phone.InterfaceConsts.ContactId + " = ?",
                new[] { contactId },
                null
            );

            return ReadCursorItems(cursor, key)?.ToArray();
        }

        /// <summary>
        /// 读取联系人邮箱地址
        /// </summary>
        /// <param name="ctx"></param>
        /// <param name="contactId"></param>
        /// <returns></returns>
        string[] GetEmails(Context ctx, string contactId)
        {
            var key = ContactsContract.CommonDataKinds.Email.InterfaceConsts.Data;

            var cursor = ctx.ApplicationContext.ContentResolver.Query(
                ContactsContract.CommonDataKinds.Email.ContentUri,
                null,
                ContactsContract.CommonDataKinds.Email.InterfaceConsts.ContactId + " = ?",
                new[] { contactId },
                null);

            return ReadCursorItems(cursor, key)?.ToArray();
        }

        IEnumerable<string> ReadCursorItems(ICursor cursor, string key)
        {
            while (cursor.MoveToNext())
            {
                var value = GetString(cursor, key);
                yield return value;
            }

            cursor.Close();
        }

        string GetString(ICursor cursor, string key)
        {
            return cursor.GetString(cursor.GetColumnIndex(key));
        }

    }
}

Plugin.CurrentActivity and Acr.UserDialogs packages need to be added .

7. Android project adds permission handling judgment class

Permission.Util

using Android.Content.PM;

namespace TerminalMACS.Clients.App.Droid
{
    public static class PermissionUtil
    {
        /**
		* 通过验证给定数组中的每个条目的值是否为Permission.Granted,检查是否已授予所有给定权限。
		*
		* See Activity#onRequestPermissionsResult (int, String[], int[])
		*/
        public static bool VerifyPermissions(Permission[] grantResults)
        {
            // 必须至少检查一个结果.
            if (grantResults.Length < 1)
                return false;

            // 验证是否已授予每个必需的权限,否则返回false.
            foreach (Permission result in grantResults)
            {
                if (result != Permission.Granted)
                {
                    return false;
                }
            }
            return true;
        }
    }
}

MainActivity.OnRequestPermissionResult is a permission application result processing function. In this function, ContactsService.OnRequestPermissionsResult is called to notify the address book service permission processing result.

MainActivity.cs

using Acr.UserDialogs;
using Android.App;
using Android.Content.PM;
using Android.OS;
using Android.Runtime;
using TerminalMACS.Clients.App.Droid.Services;
using TerminalMACS.Clients.App.Services;

namespace TerminalMACS.Clients.App.Droid
{
    [Activity(Label = "TerminalMACS.Clients.App", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        IContactsService contactsService = new ContactsService();
        protected override void OnCreate(Bundle savedInstanceState)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(savedInstanceState);

            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
            UserDialogs.Init(() => this);

            // 将通讯录服务实例传递给共享库,由共享库使用读取通讯录接口
            LoadApplication(new App(contactsService));
        }
        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
        {
            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

            // 通讯录服务处理权限请求结果
            ContactsService.OnRequestPermissionsResult(requestCode, permissions, grantResults);
            
            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}

8. Create AddressBook ViewModel and use the address book service

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using TerminalMACS.Clients.App.Models;
using TerminalMACS.Clients.App.Services;
using Xamarin.Forms;

namespace TerminalMACS.Clients.App.ViewModels
{
    /// <summary>
    /// 通讯录ViewModel
    /// </summary>
    public class ContactViewModel : BaseViewModel
    {
        /// <summary>
        /// 通讯录服务接口
        /// </summary>
        IContactsService _contactService;
        /// <summary>
        /// 标题
        /// </summary>
        public new string Title => "通讯录";
        private string _SearchText;
        /// <summary>
        /// 搜索关键字
        /// </summary>
        public string SearchText
        {
            get { return _SearchText; }
            set
            {
                SetProperty(ref _SearchText, value);
            }
        }
        /// <summary>
        /// 通讯录搜索命令
        /// </summary>
        public ICommand RaiseSearchCommand { get; }
        /// <summary>
        /// 通讯录列表
        /// </summary>
        public ObservableCollection<Contact> Contacts { get; set; }
        private List<Contact> _FilteredContacts;
        /// <summary>
        /// 通讯录过滤列表
        /// </summary>
        public List<Contact> FilteredContacts

        {
            get { return _FilteredContacts; }
            set
            {
                SetProperty(ref _FilteredContacts, value);
            }
        }
        public ContactViewModel(IContactsService contactService)
        {
            _contactService = contactService;
            Contacts = new ObservableCollection<Contact>();
            Xamarin.Forms.BindingBase.EnableCollectionSynchronization(Contacts, null, ObservableCollectionCallback);
            _contactService.OnContactLoaded += OnContactLoaded;
            LoadContacts();
            RaiseSearchCommand = new Command(RaiseSearchHandle);
        }

        /// <summary>
        /// 过滤通讯录
        /// </summary>
        void RaiseSearchHandle()
        {
            if (string.IsNullOrEmpty(SearchText))
            {
                FilteredContacts = Contacts.ToList();
                return;
            }

            Func<Contact, bool> checkContact = (s) =>
            {
                if (!string.IsNullOrWhiteSpace(s.Name) && s.Name.ToLower().Contains(SearchText.ToLower()))
                {
                    return true;
                }
                else if (s.PhoneNumbers.Length > 0 && s.PhoneNumbers.ToList().Exists(cu => cu.ToString().Contains(SearchText)))
                {
                    return true;
                }
                return false;
            };
            FilteredContacts = Contacts.ToList().Where(checkContact).ToList();
        }

        /// <summary>
        /// BindingBase.EnableCollectionSynchronization 为集合启用跨线程更新
        /// </summary>
        /// <param name="collection"></param>
        /// <param name="context"></param>
        /// <param name="accessMethod"></param>
        /// <param name="writeAccess"></param>
        void ObservableCollectionCallback(IEnumerable collection, object context, Action accessMethod, bool writeAccess)
        {
            // `lock` ensures that only one thread access the collection at a time
            lock (collection)
            {
                accessMethod?.Invoke();
            }
        }

        /// <summary>
        /// 收到事件通知,读取一条通讯录信息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnContactLoaded(object sender, ContactEventArgs e)
        {
            Contacts.Add(e.Contact);
            RaiseSearchHandle();
        }

        /// <summary>
        /// 异步读取终端通讯录
        /// </summary>
        /// <returns></returns>
        async Task LoadContacts()
        {
            try
            {
                await _contactService.RetrieveContactsAsync();
            }
            catch (TaskCanceledException)
            {
                Console.WriteLine("任务已经取消");
            }
        }
    }
}

9. Add address book page to display address book data

<?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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
             mc:Ignorable="d"
             Title="{Binding Title}"
             x:Class="TerminalMACS.Clients.App.Views.ContactPage"
             ios:Page.UseSafeArea="true">
    <ContentPage.Content>
        <StackLayout>
            <SearchBar x:Name="filterText"
                        HeightRequest="40"
                        Text="{Binding SearchText}"
                       SearchCommand="{Binding RaiseSearchCommand}"/>
            <ListView   ItemsSource="{Binding FilteredContacts}"
                        HasUnevenRows="True">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <StackLayout Padding="10"
                                         Orientation="Horizontal">
                                <Image  Source="{Binding Image}"
                                        VerticalOptions="Center"
                                        x:Name="image"
                                        Aspect="AspectFit"
                                        HeightRequest="60"/>
                                <StackLayout VerticalOptions="Center">
                                    <Label Text="{Binding Name}"
                                       FontAttributes="Bold"/>
                                    <Label Text="{Binding PhoneNumbers[0]}"/>
                                    <Label Text="{Binding Emails[0]}"/>
                                </StackLayout>
                            </StackLayout>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

Three, source code acquisition

The compiled Android client: https://terminalmacs.com/terminalmacs-clients-app-android

  • 3. iOS reading address book function code has also been added, but since I do n’t have an iOS test environment, it has not been verified. Conditional friends can test the iOS address book reading function. If the code does not work, you can refer to this article for reference The article checks the iOS code.

4. Reference materials

Getting phone contacts in Xamarin Forms:https://www.xamboy.com/2019/10/10/getting-phone-contacts-in-xamarin-forms/

Refer to the source code link at the end of the article.

Five, the plan behind

Xamarin.Forms client basic information acquisition, such as IMEI, IMSI, local number, Mac address, etc.

Guess you like

Origin www.cnblogs.com/lonelyxmas/p/12672749.html